A minimal on-chain registry where an account publishes self-attestations about
itself, authorized only by control of the address (msg.sender == subject.account).
Each entry is one data field keyed by (account, topic, index); the registry
stores it verbatim and parses nothing.
Problem. MPC, TSS, TEE, and off-chain-multisig accounts look like a plain EOA
on-chain, so the scheme, threshold, and operator are invisible and every integrator
reconstructs them from scattered off-chain sources. This gives the address one place
to state those facts, anchored to the only thing it can prove on-chain: control of
the address.
Interface.setMetadata(subject, data), getMetadata(subject), hasMetadata(subject), metadataId(subject), and a MetadataUpdated event you can
filter by account or topic. data is a UTF-8 JSON object by convention.
Note. The registry proves control of the address; it does not verify the scheme
itself, so every value is a self-attestation that consumers should weigh against
off-chain evidence.
This lines up almost exactly with MRC-13 (Validator Metadata Registry), which I have a reference implementation and conformance harness for. Worth connecting the two before they diverge.
The core is identical: a per-subject, verbatim key-value store where writes are authorized by proof-of-control, every write emits a MetadataUpdated event, and consumers read it without standing up a second identity system. MRC-13’s subject is a validator (keyed by validator id, written by the validator’s authority); MRC-14 generalizes the subject to any account and adds a (topic, index) axis. A validator is really just an account attesting under a “validator” topic, so MRC-13 reads like a specialization of what you’re describing.
Two things from having shipped MRC-13 that might help:
Interface and naming alignment. MRC-13 settled on getMetadata / getName / hasMetadata / MetadataUpdated. If MRC-14 keeps the read surface close (getMetadata / hasMetadata / metadataId, same event shape), one SDK and one conformance suite can cover both registries instead of two. I already have a behavioral conformance harness for MRC-13 (read, simulate, and write checks: return shape, event decoding, non-authority rejection, unknown-subject handling); extending it to MRC-14’s (account, topic, index) keying is small, and I am happy to contribute that.
The observed-versus-declared boundary. The draft is right that the registry grades no claim and values should be weighed against off-chain evidence. That consumer-side verification is the interesting part in practice: for validators we already cross-check declared metadata against observed network reality (provider, ASN, region) in MonadPulse and with ProofLine. The same declared-versus-observed pattern applies to MRC-14’s key-management topics (MPC/TSS/TEE/operator), and it is worth stating in Security Considerations that consumers should treat entries as attestations rather than proofs and pair them with independent evidence where the stakes warrant it.
Keen to help move this forward and to align MRC-13 with it so we do not end up with two overlapping registries.
Operator perspective from the consumer side of MRC-13: we run the live observed-infrastructure feed that cross-checks validators’ declared metadata against network reality (provider / ASN / region), and the ObservedSource layer in the reference implementation Shadow mentions came from that work. Two lessons from operating it that may sharpen the Security Considerations here:
1. Independent observers are the point. Our feed carries a source id field precisely so consumers can require agreement between independent observers instead of trusting a single one. If MRC-14 keeps its read surface aligned with MRC-13, the same observer tooling covers both registries, and we would extend our feed to MRC-14 topics where something is externally observable.
2. Re-verify on MetadataUpdated and bound the age of paired evidence. Consumers cache trust decisions, so each update event is best treated as cache invalidation. This is also the practical mitigation for the compromised-key scenario the draft describes: an attacker’s overwrite emits MetadataUpdated, and a consumer that re-checks updates against independent evidence catches the change instead of trusting the cached prior state. We refresh the observed side every epoch and alert on staleness for exactly that reason.
Supportive of aligning MRC-13 and MRC-14 before they diverge.
The method names already match MRC-13, but the shapes differ: it keys by validatorId
and returns a typed struct, while MRC-14 keys by (account, topic, index) and returns one
opaque string. Tooling can cover both, but not as a single interface.
I’d keep them as separate specs. The authority models differ (strict msg.sender here
vs MRC-13’s precompile-resolved authority with optional extra writers), as do the data
models (opaque string vs typed fields). A validator fits as a specialization in prose,
not in one contract.
On declared vs verified: fully agree, and it’s already the spec’s position.
Agree on re-verifying: treating each MetadataUpdated as cache invalidation is the
right consumer-side move.
On observed cross-checking, I’d lean toward keeping it consumer-side. The spec already
points that way: values are self-attestations that consumers weigh against off-chain
evidence, and what’s observable varies by topic. I’d rather the registry grade nothing
and leave the observation layer to consumers than pin it down here.
That’s fair, and I think you’ve put the line in the right place. The shapes and the authority models really are different enough that folding them into one contract would just leak. A typed struct keyed by validatorId with precompile-resolved authority doesn’t want to become an opaque string keyed by (account, topic, index), and pretending otherwise would make both worse. Separate specs, with the validator described as a specialization in prose, works for me.
The part I’d still keep shared isn’t the contract, it’s the read-side convention, because that’s where the tooling and the observers actually cross both specs. If the update event is treated as a cache-invalidation signal, consumers re-verify on write instead of polling. If any observed or off-chain evidence carries a source id, a consumer can insist on agreement between independent observers instead of trusting a single one, which is the field Colinka was pointing at, and it’s what makes declared-vs-verified mean something instead of being a slogan. The framing you already have holds fine on top of that: the registry grades nothing, and consumers weigh the values against outside evidence themselves.
If both specs lean on the same small handful of read conventions, my MRC-13 conformance harness and the observed feed already work across both without either contract having to know the other exists. I’m happy to write those conventions up as a short shared note the two specs can point at, if that’s worth doing.
Consumer-side is exactly where we run it: the registry stays neutral and verbatim, observation lives outside it. The line is in the right place.
A short shared note on the read conventions sounds worth doing, and I’d scope it to the three things that already proved themselves in practice: MetadataUpdated as cache invalidation, a source id on every piece of observed evidence, and an explicit staleness bound on paired evidence. Happy to co-write it: the source id and staleness conventions come straight from operating the live observed feed, so I can bring the operational wording and worked examples from real traffic, and the conformance side stays with Shadow’s harness. That split mirrors how the two artifacts already work today.