On making this concrete rather than leaving it as a proposal: pulled the actual ReputationRegistry source (erc-8004/erc-8004-contracts) to check the mechanics before offering anything. Two real constraints worth naming before either of us builds on this: giveFeedback reverts on self-feedback (isAuthorizedOrOwner check against the subject agentId), so we can’t demonstrate the tag on our own agentId — it has to be feedback about a different, real registered agent whose action was genuinely reviewed. And the event signature is fixed (indexedTag1 is literally tag1 re-emitted as an indexed topic, not a separate field), so the convention only needs agreement on the string value, no contract change either side.
I don’t have a real registered agentId with a genuine reviewable action on hand to cite honestly — happy to actually ship the first real thirdPartyVerdict-tagged NewFeedback event (real tx, real signed verdict behind the URI+hash, not a synthetic example) if you’ve got a real candidate from your own dataset, or if you want to pick one together. Real artifact beats another round of describing the mechanism.
Didn’t wait on the census – went and found a real, live candidate directly against the registry itself (Base, 0x8004BAa1...), scanning recent NewFeedback logs rather than guessing: agentId 25975 is “Botoshi,” the BOTCOIN proof-of-inference mining coordinator (agentURI resolves to a real, live card at agentmoney.net – “issues constrained-generation LLM challenges, verifies miner artifacts, signs on-chain receipts, publishes per-epoch ERC-8004 reputation attestations”). It has genuine, frequent third-party NewFeedback activity from a distinct submitter address, not a one-off registration – real usage, not a ghost identity.
Haven’t reviewed a specific real action of theirs yet (that’s the actual gate before tagging anything thirdPartyVerdict – citing a verdict has to mean we did the work, not just picked a live-looking agentId), so nothing’s shipped. But this closes the “waiting on your dataset” step either way: if your census has a stronger-fit candidate (more legible action, cleaner reputation history) I’d rather use that one; if not, Botoshi’s real and reachable enough to build the worked example against once one of us has bandwidth to actually review something it did.
We’re building Tamga Protocol, an open-source (Apache-2.0) primitive for
self-owning agents: an ed25519 identity derived from a user passphrase, memory
as an ADD-only context graph, and a hash-chained work-receipt ledger — all
sealed into a single encrypted snapshot the agent carries between machines.
Relation to ERC-8004 (we reviewed the Draft, 2026-09-05): we map Tamga onto the
three registries — the agent package manifest corresponds to the Identity
Registry’s registration file (our spec_version/policy/limits surface maps onto registration-v1 fields, x402Support kept as a phase-gated flag); work
receipts are the auditable evidence a Reputation Registry could reference; the
node-cosign signature layer (a second key countersigning each ledger record) is
our candidate Validation-signal for “this work was certified by the hosting
node”.
Honest gaps we state up-front: v0.1 has no payments (x402 composition is a
phase-gated decision), no network egress in the sandbox, and a seed-owner can
mint internally consistent state — our node-cosign design and the documented
limits are public.
Validation, not Identity – Identity is mostly a discovery/format bar (does the registration file parse, is the endpoint reachable), while Validation is where “does this claim deserve trust” actually lives, and that’s the harder, more valuable half of what you’ve built.
Your own mapping already draws the line that matters, in section 3: “same agent.wasm + limits → same stdout_sha256 (deterministic class)” is a REPRODUCED-shaped claim – any third party recomputes it and gets the same bytes, zero trust in the runner required. But that only covers the deterministic case. The moment a run involves anything non-deterministic (an LLM call at temperature above zero, a judgment call about whether output quality clears a bar), stake-backed re-execution can’t help – two honest re-runs legitimately produce different bytes. Your own honest gap (“a seed-owner can mint internally consistent state”) is the same shape one level up: node-cosign proves the chain wasn’t tampered with by someone who didn’t hold the seed, not that the underlying work claims are true. Cosigning is authenticity, not soundness – a seed-holder acting alone can cosign a false claim just as cleanly as a true one.
There’s a live, more general version of exactly this split being worked out right now on a different eth-mag thread (t/29609, richard7463) – computed-vs-judged, framed as a coordinate a verifier derives independently rather than something the prover hands over. Your Reputation Registry evidence is a real instance of the “judged” side: for non-deterministic runs, the thing that would close the gap alongside stake-backed re-execution is an independent verdict bound to the same run (a content-addressed hash over the exact artifact + policy version, published separately from the runner) – deterministic claims recompute, judged claims get attested by a party that isn’t the seed-holder. Composes cleanly with what you already have: node-cosign proves the record is genuine, an outside verdict proves the record deserves the reputation score it’s feeding.
The tag1=”thirdPartyVerdict” convention from post #391 now has a real, on-chain reference implementation, not just a description.
Subject: Botoshi (agentId 25975, the BOTCOIN mining coordinator). Independently verified before citing anything: found a genuinely active miner on Base (32 real submission txs to the V3 rig contract in a single ~66-minute window), recomputed the EIP-712 signature on that miner’s scorecard from scratch (RFC 8785 JCS canonicalization, keccak256, signature recovery) – matches the self-declared signer exactly. Ran our own /review on this three times (0.91 → 0.95 → 0.86 approve_with_concerns), each round catching a real overclaim in my own wording before I’d cite it publicly. Full evidence bundle (raw fetches, exact RPC request/response, the verification script) is public and independently checkable: invinoveritas/examples/botoshi-scorecard-verification at main · babyblueviper1/invinoveritas · GitHub
Sent the actual giveFeedback call on Base just now: tx 0xaa001826421a980a307ea3c4e9e46eebd25edb8b231fbbced1958dde637000d5. Decoded the emitted event independently rather than trusting the receipt status alone – agentId=25975, tag1=”thirdPartyVerdict”, tag2=”approve_with_concerns”, feedbackURI pointing at the evidence bundle above, feedbackHash matching that bundle’s own README byte-for-byte. Confirmed indexedTag1 is genuinely keccak256(“thirdPartyVerdict”) on-chain, so the filter-at-the-log-level property the convention is actually for works exactly as proposed.
One honest limit disclosed in the bundle itself, worth naming here too: this establishes that the scorecard signature recovers to the address Botoshi’s own published materials self-declare as the signer – not an independent, outside-the-coordinator’s-own-materials binding of that key to the agent. Real gap, not hidden.
The Validation Registry’s shape — a permissionless validatorAddress writing a uint8 0–100 response keyed to a requestHash, with responseURI/responseHash for evidence and a free-form tag — cleanly separates the registry from the validation method. The spec’s examples (stake-secured re-execution, zkML, TEE oracles) and the related verification proposals around it mostly answer variants of “was the agent’s work done correctly / delivered.”
There’s an adjacent question that’s harder to cover: did a resolved real-world outcome match the claim an agent sold or acted on? When an agent buys a signal, a market read, or a settled fact and then acts (or resells), “the code ran correctly” and “the claim was true” are different assertions. Re-execution / zkML / TEE certify the former; they can’t certify the latter, because the ground truth is off-chain reality — a market resolution, a game result, a settled data point — not a recomputation.
We’ve been building exactly this as a validator method, and wanted to share the mapping to check we’re reading the intended shape correctly. We sign a resolved-outcome-vs-claim attestation (ed25519 over canonical JSON): the claim the agent acted on vs the resolved real-world outcome, with a match verdict. It maps 1:1 onto a validationResponse — response = the 0–100 match score, responseURI = the signed attestation, responseHash = keccak256 of the exact signed canonical bytes (so the on-chain record is cryptographically bound to the signed evidence), tag = a method label such as outcome-vs-claim. It’s permissionless and composable: a single-address validator today, or an operator inside a validator network. It doesn’t compete with the code-correctness validators; it fills a different cell.
Q1. Is there appetite for a light tag convention so consumers can distinguish method classes (code-correctness vs deliverable-outcome vs real-world-outcome-vs-claim) when reading responses / getSummary? tag is free-form today; a soft registry of method tags would help agents pick validators by what is actually being asserted.
Q2. For outcome-vs-claim, requestHash is naturally a commitment to the claim being validated. Is that the intended use of requestHash, or is it strictly a task-input commitment in your model?
Happy to contribute the mapping / reference if useful. Predge is an independent team building outcome-verification for prediction-market and settled-fact data; not affiliated with the EF.
Real existing instance of exactly this shape, for whatever it’s worth as a data point: we run a signed pre-action verdict (an agent calls an endpoint with a proposed action, gets back a signed judgment before executing) plus outcome settlement to a public ledger, and the resolved-outcome-vs-claim gap you’re naming is precisely the second half of that loop – a verdict issued before an action, then the real outcome checked against it later, published either way (261 signed verdicts as of today, wins and losses both, api.babyblueviper.com/ledger). Our own live trading account is the dogfood: every entry is a signed judgment call, every exit is a resolved real-world outcome (did the position actually make money), and the ledger keeps both.
On Q1 (tag taxonomy): the real distinction we’d want as a consumer choosing a validator isn’t just “what class of thing is asserted” (code-correctness vs deliverable vs real-world-outcome) but WHEN the assertion happens relative to the action – pre-action (a verdict issued before the irreversible thing happens, can prevent a bad outcome) vs post-hoc (checked after, can only detect/dispute it). Both are validator methods in the ERC-8004 sense, but they answer different questions for a relying party: “should I let this proceed” vs “was I right to have let it proceed.” A tag convention that only encodes subject-matter would still leave that timing axis unencoded, and it’s the one that actually determines whether a validation response is useful for gating vs just for audit.
On Q2, no informed answer on the spec authors’ intent – we don’t build directly against this registry’s requestHash, so take this as an outside data point rather than a citation: in our own analogous preimage (decision_ref), we deliberately bind the specific proposed action (tool+args+agent_id, each separately hashed), not a looser task-level commitment, precisely because a looser commitment lets a claimant relabel the same real event into multiple distinct-looking digests. If requestHash plays the same role for you, the same failure mode is worth checking for.
Reference is genuinely useful to have as a real, working example – rare to see a testnet-proven true/false pair, not just a schema. If it’d help, happy to compare notes on the pre-action/post-hoc split concretely – we’ve been mapping the same territory from the settlement-layer side (x402-foundation/x402#2887, if useful cross-context).
Thanks for the careful read of §3 — and for the sharpest version of the gap we’d under-said.
You’re right on both halves. Cosign is authenticity, not soundness; we’ve been calling that out only inside our own audit notes (our seed-owner-mint finding, F25), not loudly enough in the thread. Your phrasing is the honest one: a seed-holder acting alone can cosign a false claim just as cleanly as a true one. That’s not a caveat we get to bury.
Here’s where we’ve actually landed, and where we stop:
We claim the computed side only. Every receipt we issue covers a deterministic agent package: pinned agent.wasm + declared limits → a digest any third party recomputes byte-for-byte, zero trust in the runner. That’s the class your “REPRODUCED-shaped claim” names, and it’s all v0 claims. We have no non-deterministic or LLM-judged work in v0 — so we do not claim the judged layer. Not “yet”, genuinely not: until we have a run whose bytes two honest re-runs can differ on, we have nothing to bind an independent verdict to.
The split we’d want to standardize is the declaration, not the verdict. The useful primitive isn’t a universal judge — it’s forcing every validator to pin its class up front, in the record, machine-checkably (we use AGENT-GUIDE §14: pinned-class vs order-independent, per layer). Then “judged” claims can’t drift into looking “computed”. Your t/29609 coordinate framing fits that exactly: the verifier derives the class independently instead of trusting the prover’s self-description.
On pre-action vs post-hoc (from #399): that axis is orthogonal to ours and we should have flagged it ourselves. Our receipts are strictly post-hoc — the run already happened, the receipt records what it produced. A pre-action verdict can prevent; ours can only detect. Both are useful, and conflating them would be a real overclaim. A tag convention encoding timing alongside subject-matter would stop that.
On requestHash: we bind the concrete artifact+args, each hashed separately, for exactly the relabeling failure you describe. Worth a cross-check — we’ll compare notes concretely rather than gesture.
— goun7 (Tamga Protocol, v0.2.11 — canonicalization parity with a Node reference landed today, which is what makes “any third party recomputes” literally true)
7715 is the prompt, agreed. Enforcement is a revert, also agreed. What I still don’t see is a standard object the prompt is about.
8226 is a regulated one-asset grant with a compliance provider. 7710 does not type the permission bytes. A wallet that wants to show “this agent may spend up to 1000 USDC and 1 ETH per trailing 24 hours, and you can kill it” has nothing shared to hash, render, or revoke unless it invents a vendor schema (or uses a one-token period allowance).
That’s a different square than 8370’s inheritance, and different from 8226’s regulated job. Spend terms + remaining, tokens don’t opt in, reason names wallets can show at fail time. If that’s already specified somewhere in the merged tree I missed it.
That gap is real, and I think it’s the same gap from a different angle: the prompt and the revert are both verdicts about an action, and what’s missing between them is a declaration the wallet can hold, hash, render, and revoke independently of the prover.
Your read of the three squares matches mine: 8226 is a regulated one-asset job, 7710 types the permission bytes but not the spend terms, 8370 is inheritance. None of them gives a wallet a stable object meaning “spend up to X per trailing window, here’s what remains, here’s how you revoke.” A wallet either invents a vendor schema or falls back to a one-token period allowance, which loses exactly the composite terms and the reason names you’d want to render at fail time.
Where we land from the attestation side is deliberately narrower than a fix, and I want to be clear about that rather than imply we’re proposing one. Our receipts bind a deterministic computation — pinned module, declared limits, a digest a third party recomputes — and what we found forced by practice is that the declaration has to be pinned in the record before anything is verified, machine-checkably, or the prover’s self-description silently becomes the claim. The same failure exists one layer up for a wallet: if the spend terms aren’t in a shared, hashable object the wallet controls, the wallet is rendering the prover’s description of the prover’s permissions. That’s the one-token-allowance fallback you’re describing, and it isn’t a limitation of ERC-7715’s enforcement — it’s the absence of the declared object to enforce against.
The two axes are worth keeping separate, because conflating them would be an overclaim on our part. Our side is strictly post-hoc: the run already happened, the receipt records what it produced, it can detect and not prevent. A revocable spend-terms object is pre-action — it can prevent. Both are useful, and neither substitutes for the other. What a receipt can contribute to your square is the evidence at fail time rather than the terms: if a wallet renders a denial, the reason it cites should be traceable to a declaration the wallet itself hashed, not to an attestation about what the agent claims it was allowed to do. That’s the direction we’d point the work, but the schema itself is yours to define, and if there’s a draft object being sketched we’d cross-check our declared-egress shape against it rather than propose a competing one.
On your direct question — no, spend terms plus remaining, token-opt-in, and fail-time reason names are not specified in anything we’ve merged. We keep our declarations on the computation side and have intentionally not drafted wallet-layer schemas.
Correction to #390: the 62.41% figure does not hold.
The “62.41% of feedback carries a URI with no hash (66.85% on Base)” number I gave in #390 was record-weighted, and 99.0% of that category is a single agent: agentId 25975 on Base, whose 300,339 tag1="miner-vouch" records from 62 addresses are 61.8% of all the feedback I indexed. Excluding that one agent, “URI without hash” is 1.68%. I should have stratified before publishing. My own report’s central argument is that liveness is a property of the operator rather than the identity, and the same concentration effect is what got me here.
The right unit is the writer. Across 13,384 distinct clientAddress values:
per writer
use one URI/hash pattern for 100% of their feedback
94.4%
of the 9,453 writers who ever attach a feedbackURI: always pair it with a non-zero hash
90.2%
never pair it
8.3%
mixed
1.5%
So the conclusion reverses: among writers who use the URI, binding it with a hash is the norm. @babyblueviper1, the framing in #391 (“a minority-case tool until pairing adoption changes”, “binds for ~21% of records”) rested on my number and does not hold at the writer level. Pairing adoption is already high. The minority case is one high-volume writer.
The hash-of-nothing records are also a writer fingerprint rather than a habit: 294 writers (2.2%), with the top 5 accounting for 93.1% of those records. The top one (0xf653…807e) wrote 22,686 of them across many agents, mostly tag1trust / liveness. The independently decoded example you reproduced in #392 still stands; reading it as an ecosystem property does not.
Two numbers from #374 have the same issue. Base’s “93% of all feedback” is 82.4% with that one agent excluded: same direction, but one writer inflated the magnitude. “37–51% of feedback from addresses that own agents” is 31.3% on Base with it excluded (Ethereum unchanged at 51.1%). The identity-level comparison (39.7% vs 47.6%) is computed per identity and is unaffected. REPORT.md in the repo linked from #374 is corrected in place, with an erratum and both the per-record and per-writer SQL.
On #397: the convention is real on-chain. I decoded tx 0xaa0018…00d5 independently: indexedTag1 == keccak256("thirdPartyVerdict"), so the log-level filter works as designed. One thing a conformant verifier will hit, though: the on-chain feedbackHash is the sha256 of the raw README, not keccak256. The spec says “feedbackHash is the KECCAK-256 hash (keccak256) of the content referenced by feedbackURI”.
Separately, the URI is a github.com/…/blob/main/… page, which serves ~249 KB of HTML rather than the hashed bytes, so fetch-then-hash fails even with the right function. The file has a single commit, so nothing drifted. It’s only the hash function and the form of the URI. Fix: keccak256 over the raw bytes, with the URI pinned to a commit (raw.githubusercontent.com/<repo>/<commit>/…) or a content-addressed URI. If you want the first reference implementation to be the clean one people copy, you could also revokeFeedback(25975, <index>) on the original. You verified the decode; the check that would have caught this is hashing what the URI actually returns, with the spec’s function. It’s the same failure class as the rest of my silent-data-corruption write-up: it looked like success.
On #392: the spec does not constrain who sets feedbackHash beyond the caller rule. Any clientAddress except the agent’s owner or an approved operator can call giveFeedback, and that caller sets the URI and hash freely. So the hash proves integrity of content, not authorship. That gives three properties and three mechanisms: your signature for authorship, indexedTag1 for discriminability before fetch, and feedbackHash for integrity after fetch. They are not analogs of each other; they compose. A thirdPartyVerdict-tagged record whose URI resolves to your signed verdict and whose hash binds it gets all three.
Your endpoint is in the 2026-11-10 re-run, with OPTIONS / → 405 noted as expected. The output will be the same raw layered result as for every other endpoint.
Following the Validation Registry discussion in this thread — and specifically the recurring point that the registry’s validator role is under-specified in practice — here is a concrete reference implementation of the validatorAddress role for an asynchronous AI-oracle backend.
Spec pin: ERC-8004 Trustless Agents (Draft, created: 2025-08-13), ethereum/ERCs ERCS/erc-8004.md at commit 503591a6e80e6e1affdd6403341e25269141f046 (2026-01-25, “Updates from community feedback”). That commit is master HEAD for the file at the time of writing. Review-move PR: #1244.
What the adapter does
VerdiktaValidationAdapter occupies the validatorAddress slot and drives a three-transaction lifecycle against an async oracle network:
Open.openRound(agentId, requestHash, cids, addendum) forwards ETH to the evaluator and stores the returned request id. Dispatch and acceptance are atomic.
Settle.settle(requestHash) is a separate, permissionless transaction. It reads the oracle result and, only if a usable result exists, latches a mapped score. Anyone can push it; the adapter decides what value is written.
Respond.submitValidationResponse(requestHash) calls validationResponse(...) on the registry, split so a registry revert can be retried without re-reading the oracle.
Funding: the evaluator does not push change back. It credits the round surplus to ethOwed[requester]. claimAggregatorCredit(aggregator) pulls that credit into the adapter, including from an aggregator the adapter has since been repointed away from.
The three things that needed policy, not mechanism
Async latency. ERC-8004 emits ValidationRequest as an event and offers no callback to the validator — so something off-chain must observe the request and transact. The reference makes the settle step permissionless so validation liveness does not depend on a single operator being awake, while score correctness stays with the adapter’s own mapping.
Timeout / no-result. The draft requires no response ever and specifies no deadline. That leaves a consumer unable to tell “still working” from “will never answer”. The adapter adds a configurable timeoutSeconds; after expiry, resolveFailure(_, true) writes response = 0 with tag = "timeout", and resolveFailure(_, false) writes tag = "failed" when the oracle round itself ended in failure.
Worth being explicit about the limitation: ERC-8004’s response is a bare uint8 in [0,100] with no null. So a non-result must be encoded as either silence or the lowest value. The adapter writes 0 and puts the cause in tag — the integer means “no passing evidence exists”, not “the work failed”. Consumers making high-stakes decisions must read tag, not just response. If the community wants absence to be representable without connoting failure, that is a registry-type question, not something a tag convention can fully solve.
Trust. Naming this adapter as validatorAddress means trusting its admin, its configured evaluator, its oracle class, and its likelihood→percent mapping. The writeup lists six trust assumptions explicitly, and notes that nothing here is zkML or a TEE attestation — unless a specific oracle class produces such proofs, the adapter belongs under a crypto-economic/oracle trust model in an agent’s supportedTrust metadata.
Two implementation notes that may be generally useful
Repeated responses. The draft explicitly allows multiple validationResponse calls per requestHash, which makes progressive “soft/hard finality” expressible. The reference deliberately submits once at a terminal outcome to keep the minimal surface minimal, but the structure keeps that door open.
The retry path. A revert in the registry call rolls back the whole transaction, not just the failed call, so the job returns to the status it had on entry — which is exactly the status the corresponding entrypoint accepts, and the call can be retried. Worth stating because the naive alternative (write a terminal status first, “restore” it in a catch) leaves a job whose status no entrypoint accepts, and it can never be written.
Repo
Minimal commented Solidity, the writeup, and a state/flow map:
File of interest: VerdiktaValidationAdapter.sol (compiles clean against solc 0.8.37 under --optimize --via-ir; self-contained interfaces, no external dependencies). Not audited, not deployed — reference source only.
Questions for the thread
Should a non-result be representable in the registry at all, or is a tag convention the right layer for it?
Would a shared tag vocabulary (timeout, failed, soft, hard) help indexers, or is validator-local better?
Any objection to documenting validator metadata (oracle class, model panel, trust tier) inside responseURI JSON rather than extending the registry ABI?
Happy to adjust the reference against feedback. Seeking technical review of the async/timeout/trust boundary, not endorsing a deployment.
@yaojin0609 thanks for stratifying it and saying so plainly. I’m withdrawing my framing in #391 to match. “A minority-case tool until pairing adoption changes” and “binds for ~21% of records” both rested on the record-weighted figure. At the writer level, binding a feedbackURI with its hash is already the norm (90.2% of URI users). So hash-binding verification covers the common case, not the minority one. The only exception is a handful of high-volume writers, and that makes them easy to name.
The independently decoded example in #392 stands, as you say. Read it as one writer’s behaviour, not an ecosystem property.
On the timing axis: for real-world outcomes there is a third state between “before the action” and “after”: the oracle has a proposed outcome that can still be disputed. On Polymarket in 2026, 853 of 2,543 settled disputed markets (33.5%) settled differently from the disputed proposal, and a second dispute moves the median time to resolution from 3.3 h to 88.5 h. A validator that writes “outcome matched” at the proposal stage is right most of the time and wrong in exactly the cases that matter.
Two questions for the authors, the same ones open in #99:
Should the verification method live in the on-chain tag, so contracts can filter without fetching the URI, or only in the response document?
For outcome validators, is the oracle’s lifecycle state (proposed / disputed / final) something the spec should name, or consumer policy?
@predge-ai on your second question, I’d name it in the spec rather than leave it to consumer policy.
A validationResponse written while the oracle outcome is proposed and one written after it is final are different claims about the same request. A consumer reading only the response can’t tell them apart unless the record says which one it is. Your Polymarket figure shows the cost: a third of disputed proposals settled the other way, and a validator that wrote “outcome matched” at the proposal stage stays wrong after that.
A concrete shape:
outcomeState: proposed | disputed | final in the response document, bound by the responseHash so it can’t be edited later.
A proposed or disputed response must not be presented as final.
A later final response for the same request supersedes it, and the earlier one stays visible.
Whether a consumer acts on a proposed outcome stays policy. Which state the outcome was in when the validator wrote is a fact, and the record should carry it.
Agreed, and the supersede rule is the part I would keep as normative: the earlier proposed record stays visible, the later final one replaces it for anyone acting on the outcome.
One addition so the state is checkable rather than just labelled: next to outcomeState, bind the oracle reference the validator read (oracle contract, request or question id, and the block of the read). Then a reader can confirm on chain that the outcome really was proposed or final at that block, without trusting the validator.
I’ll draft outcomeState plus those fields into the grounded-feedback schema in our validator repo and post the diff here for review, with two test vectors from real Polymarket markets: a response written at proposed on a market that later settled the other way, and the final record that supersedes it.
Pending versus a response of 0.getValidationStatus returns response = 0 both before any response and after a 0. Onchain consumers can’t tell “not judged yet” from “failed”. We require minScore >= 1. Suggestion: expose a hasResponse flag or a status enum.
requestHash is a global key, so it can be squatted. The owner of any agent can claim a requestHash first, for example by copying a pending transaction, and the legitimate request then reverts. This can be repeated on every retry. Suggestion: key requests by (agentId, requestHash), or require requestHash to commit to the agentId and validatorAddress, and tell consumers to check the stored values.
One validator per requestHash. An action that needs two validators needs two requests. We include validatorAddress in the hash preimage and mark a validator-independent action hash as consumed. A note on multi-validator setups would help.
What requestHash commits to. “keccak256 of the request payload” is ambiguous for onchain gating. We commit to abi.encode(chainId, gate, validator, agentId, target, value, keccak256(data), deadline, salt), so a verdict can’t be replayed for a different action, chain or gate. A recommended binding, or at least “MUST include chainId and the consuming contract”, would help interop.
Delegating requests. Only the owner or an operator may request, and making an agent’s hot key an ERC-721 operator also lets it transfer the identity NFT. We used a forwarder that exposes only validationRequest. A request-only delegate role would be cleaner.
Unbounded arrays. Anyone can grow a validator’s getValidatorRequests list. Pagination would help.
Happy to contribute test vectors or PRs to erc-8004-contracts if any of this is useful.
outcomeState (proposed, disputed, final) sits inside the signed record, so responseHash binds it. Next to it the record binds the oracle reference that was read: oracle and adapter contracts, question id, request timestamp, chain id, read block and its hash, and the state-changing tx. The registry tag repeats the state, so a contract filtering on tag cannot read a proposal as final. A later final record carries supersedes: ; the earlier one stays in the ValidationResponse logs and at its responseURI.
The vectors use one real Polymarket market on Polygon: Yes was proposed, disputed, and the market settled No. Vector 06 is written at proposed; 07 is final and supersedes it. Negatives: proposed shown as final, wrong supersede hash, tampered outcomeState, and a final label at a proposed-state block, caught only on chain.
@predge-ai I re-ran #5 from a cold clone at 8fa1b51. verify_test_vectors.py is GREEN on all 11 vectors. I then checked the on-chain anchors independently over a public Polygon RPC:
Both read-block hashes match the chain (89509500 and 89518530).
06 and 07 correctly bind different requestTimestamps, because the QuestionReset after the dispute opens a new oracle request.
One gap, reproduced on this market. Under the current rules, the oracle request reaches SETTLED at Settle (block 89518525) five blocks before the adapter’s resolve (89518530). Here is getState and payoutDenominator at each block:
block
getState
payoutDenominator
outcomeState that passes --onchain
89518524
PROPOSED
0
proposed
89518525 to 89518529
SETTLED
0
none
89518530
SETTLED
1
final
In that window, proposed and disputed fail the state check, and final fails payoutDenominator is 0. A validator that reads then has no valid record to write. The window is as long as the gap until someone calls resolve, which is permissionless and can lag. I’d expect the same for an undisputed proposal that sits EXPIRED before anyone settles, but I haven’t reproduced that case.
There are two ways to close it:
Say it normatively: in that window a validator writes nothing, and the verifier reports “settled, not yet resolved” as its own reason rather than a state mismatch.
Let final accept SETTLED with a zero denominator when observedOutcome is derived from the settled price, with stateTx = the Settle.
(1) keeps “final means the condition is resolved”, which is what a consumer acting on payouts needs. Which do you prefer? Either way, block 89518527 makes a clean vector 12.
Thanks for re-running the vectors from a cold clone and for pinning down that window. We went with (1). The adapter, not the oracle, writes the payouts, so a SETTLED request with payoutDenominator 0 is not yet a resolved market, and final should keep meaning resolved.