Several EU regimes require something the investor themselves states, agrees or acknowledges: that they received a key information document before subscribing; that they understand a fund will run for more than ten years; that these are the wallets of their closely associated persons. Neither of the two major token standards models that. Both are built around what the issuer asserts and what third parties attest about the investor. So this capability is custom work under either standard, and it is the one capability that cannot be bought by picking one — building on ERC-3643, as we do, leaves it exactly where it was.
This article covers the store that holds it, CovenantRegistry, and the rule that keeps it from being collapsed into the identity layer. The document versions it binds to are in the document-anchoring article; the wider architecture is in the EU-Compliant Tokenized Securities: Smart Contract Architecture pillar. Four obligations in the issuer lane route through it: PRIIPs Article 13, the Market Abuse Regulation's Article 19(5), and ELTIF Articles 18(3) and 26. A venue's member-consent conditions fit the same schema, and are outside this article.
1. The direction distinction
Applies to every reader. Collapsing the two directions is the error this capability exists to prevent.
- A claim runs one direction: a trusted third party attests something about the investor — verified, professional, not sanctioned.
- A covenant runs the other: the investor attests something themselves — I received this, I understand this, I am not a market maker, these are my closely associated persons. They look like one capability and are not, and the five differences each have a build consequence:
| Claim — about the investor | Covenant — by the investor | |
|---|---|---|
| Who can supply it | Any trusted issuer — a KYC provider, a qualified trust service | Sometimes only the investor. Nobody else knows who a director's spouse is |
| What invalidates it | Revocation by its issuer, or a time-based refresh cadence | A new version of the document it was given against — an event, not a clock |
| What it is bound to | The investor | The investor and a document version hash |
| Where it is checked | The transfer hook, on every movement | Per covenant — mint, send or receive, and rarely all three |
| Instancing | Platform-wide — identity is per customer | Per asset — a key information document belongs to one asset, not to the investor |
The tell that they differ: making the key-information-delivery duty work required three exceptions to the claim model — not the transfer hook, not a boolean, bind the version hash. Those are not facts about PRIIPs. They are facts about covenants, and the same defect sat unfixed in two other regimes for as long as they were modelled as claims.
2. Why not just make them claim topics
Applies to every team that has an identity registry and is tempted to reuse it. The claim route looks cheaper and is not.
Encode a covenant as a claim topic and the document version has to go somewhere. Both options fail:
- A topic per version — every document revision forces a governance transaction on every asset's required-topic set, to add the new topic and retire the old. Miss one and the stale topic still satisfies the gate. Fail-open.
- The version inside the claim data — you have built the covenant store inside the identity registry, with none of the gate or attestor semantics, and claims now carry structured payloads.
The covenant model needs neither. The check is "does the investor's stored version hash still equal the current version in the document registry?" — so a revision invalidates every outstanding acknowledgement the moment it is anchored, with no configuration and no chance to forget. Fail-closed by construction. Given that a key information document is reviewed at least annually and on any material change, that difference is the whole design.
What is negotiable is storage, not semantics. Physically co-locating covenant rows beside the identity record, scoped by asset, is a reasonable implementation choice. What must not happen is treating them as claim topics with claim lifecycle.
3. What an entry is
Applies to every covenant. The registry knows how a covenant behaves, never which covenants exist.
We build CovenantRegistry with no hardcoded covenants. Every one is an issuer-configured entry through configureCovenant(), so a new regime, a client requirement or a national-law overlay is a configuration transaction, not a redeploy. That is not a convenience. National electronic-securities law — the layer that decides whether a ledger entry is the security — sits outside this design and has to pair with it per issuing Member State. A pairing only works if the socket already exists, so adding a national covenant must cost a configuration transaction and never a contract change. If any national covenant needs an extra field, the schema is wrong.
The fields, and why each has to be configurable:
| Field | What it does | Why it cannot be fixed |
|---|---|---|
| An opaque identifier | Names the covenant — never an enum | An enum is a redeploy every time a regime is added |
| A document reference | The registry entry this binds to — or none, for a pure declaration | Different covenants bind different documents; the managers' register binds none |
| A gate set — any subset of mint, send, receive | Where it is checked | The most important field. No two obligations below share a set |
| An attestor type — investor signature or operator attestation | Who may record it | For the managers' register it is forced to the investor's own signature |
| An invalidation rule — on new document version, on expiry, or never | What ends it | A revision invalidates a key information document; a refresh cadence expires due diligence. Both must be expressible |
| An effective-from date | When the entry starts biting | The cutover lever — see section 6. Without it, configuring an entry blocks the whole existing base at once |
| An applicability predicate | Who owes it — see section 5 | One regime owes different covenants to different classifications in different Member States |
A covenant's reach is decided by which registry holds it, and a registry serves one asset. That is a deployment constraint rather than a preference.
Where a fund has more than one class of units or shares, PRIIPs RTS Article 14b(1) requires a key information document prepared for each class. Two classes may share a combined document under 14b(2), and a representative class may stand in for others under 14b(3), but 14b(4) forbids a composite of several — so a multi-class fund cannot be assumed to collapse to one document. A multi-class fund therefore deploys one token per class, each carrying its own asset-scoped entry against its own document, all sharing one identity registry. That is the ERC-3643 shape, where one asset is one class by construction. Model the classes as partitions inside a single token instead and one acknowledgement satisfies the gate for every class on it — including classes whose document the investor was never shown.
⚠️ An acknowledgement does not travel between assets, and onboarding has to be built knowing that.
A classification lives in the shared identity registry; the covenant proving it lives in the per-asset store. So an investor who completes the MiFID opt-up against one token resolves to the fallback against every other one and completes it again. That fails closed — they are treated as retail, never wrongly promoted — so it is repetition rather than exposure. But a platform that assumes one signature covers every asset will be wrong about which of its investors are professional.
Making an acknowledgement span assets is a topology decision, not a field.
One registry holding the cross-asset covenants, and a second gate on each token pointed at it. Weigh it against the rule in section 8 first: two contracts that can independently refuse a transfer is how a refusal starts identifying which one fired.
What is stored per investor is four fields: given or not, the document version hash, the attestor type it was given as, and a timestamp. Hash and reference only — never the document, never the signature payload, never personal data. The per-investor, per-version delivery evidence the Regulation requires lives off-chain; what is on-chain is the provable pointer to it.
4. The gate map
Applies to every covenant. This is why the gate set is a set and not a flag.
| Obligation | Mint | Send | Receive | Why |
|---|---|---|---|---|
| PRIIPs Art 13 — key information document delivered before the retail investor is bound | ✅ | ❌ | ⚠️ | A retail subscription, and a distributor's sale to retail — not a peer-to-peer transfer, which no distributor touches |
| MAR Art 19(5) — declared managers and closely associated persons | ❌ | ✅ | ❌ | It populates the closed-period freeze's flag set — a sender-side control |
| ELTIF Art 18(3) — written warning for a retail fund with a life over ten years | ✅ | ❌ | ❌ | Pre-investment disclosure only |
| ELTIF Art 26 — retail suitability for total loss and full fund life | ✅ | ❌ | ⚠️ | Same distributor-sale caveat as PRIIPs |
Only one pair of rows shares a gate set, and even that pair differs everywhere else. PRIIPs Article 13 and Article 26 both gate mint and a conditional receive — but on different documents, for different reasons. A covenant store with one module-wide enforcement policy is wrong for most of these, and a store that only gates transfers cannot express the managers' register or the ten-year warning at all — neither is a transfer condition.
A burn is a send with nobody on the other side, and the adapter has to say so.
Two of the four rows above carry receive, and both do so only conditionally. On a burn the recipient is the zero address, which resolves to no investor, so the predicate is unevaluable and — correctly — fails closed. Evaluate the receive leg anyway and every redemption, buy-back burn and maturity burn in the instrument reverts on a rule that was never about exits. The adapter checks the sender only.
Two rows deserve a note. Article 19(5) is the one obligation in this architecture where operator attestation is structurally impossible: no operator, oracle or KYC provider can know who a director's closely associated persons are, so signCovenant() — the investor's own transaction — is the only route in, and recordAttestation() refuses. The declared wallets themselves live in a dedicated register the freeze reads, covered in the market-abuse article; the covenant is the signed declaration that register is derived from. And the ⚠️ on receive marks the case where the investor becomes bound through a sale by a distributor rather than by subscribing — the duty is the same, only the path changes.
5. Predicates — when one regime owes different covenants to different investors
Applies wherever more than one classification or jurisdiction is in play, which is every real distribution.
The common case is not "retail or not." It is one regime owing different covenants to different classifications, and sometimes in different Member States. The ten-year warning applies to retail investors in a fund whose life exceeds ten years — one classification limb the predicate evaluates, and one fund-level limb it does not, decided instead when someone chooses which covenants this asset carries. Keeping those two apart is what the rest of this section is about.
The predicate is a conjunction over two dimensions, evaluated at the gate
classification — a value on a named axis, read from the identity layer's per-axis store — and jurisdiction, read from the identity record. Both are platform-wide, and a wallet the registry has never heard of fails a registration check before either is reached.
Fund-level conditions are not predicate dimensions here, and that is deliberate
An obligation may turn on a fact about the asset rather than the investor — a fund life over ten years, a regulator's exemption from an information duty. The store is deployed per asset, so those facts are fixed and known at the moment someone configures that asset's covenants. A condition the configurer has already resolved does not need re-deriving at every transfer.
⚠️ What you give up is a guarantee, not a capability, and the distinction is the whole of it.
The conditions still exist — a twenty-year fund still owes the long-commitment warning, an exempted fund still doesn't owe the information duty. What changes is when the condition is applied: when someone decides which covenants this asset carries, rather than at every gate. Nothing on-chain detects that decision being made wrongly. An asset that should carry a covenant and doesn't will pass every transfer in silence. No unevaluable state, no event, and the diagnosis read cannot name an entry that was never configured. It is a deployment checklist with a second person signing it off, and it should be described as one rather than as a property of the contract.
The predicate therefore reads platform-wide throughout — and that is not permission to share one store across assets. The record key has no asset dimension, so a registry serving two tokens pools every acknowledgement between them. Per-asset deployment stands on the records, not on the predicate.
Six rules
- One entry per obligation and predicate. Never branch inside an entry. Retail ELTIF access is Article 26; the professional relief is Article 13(7). Two Articles, two entries — an entry with an internal if-retail-else-professional cannot be mapped one-to-one to an Article, and that map is the audit deliverable.
- Predicates are evaluated at the gate, never precomputed into a stored flag.
- Evaluate against the state at that moment; the record stores what was true when given. A professional-only offer that later admits a single retail investor triggers the key-information duty from that point. A tier change makes new entries applicable at the next gated action; it never retroactively invalidates a covenant properly given.
- Classification-establishing covenants resolve first. The MiFID II Annex II opt-up from retail to professional is itself a covenant — a written request, the prescribed warning, and two of three quantitative criteria — and its effect is to change the classification every other predicate reads. Resolve it before any predicated entry, or the evaluation is circular. The contract cannot enforce that ordering on its own, because the classification is written by the off-chain identity service. Two ways to hold it: as a process control, where the identity service never writes an elective classification without a recorded opt-up and the two are reconcilable for audit; or, where retail distribution is in scope,
setClassifier()names the entry and the store reads it on every gate.
On the read side, an elective classification with no satisfied covenant resolves to the configured fallback — retail, for MiFID — so the retail covenants stay live for someone the regulation still treats as retail; that one binds unconditionally.
On the write side there is nothing, and the reason is structural rather than an omission.
A check the identity registry could call befoe writing would have to ask some covenant store whether the investor signed. But the identity registry is one shared instance and the stores are per asset, so there is no single one that knows. The ordering is held off-chain or not at all, which makes the reconciliation a named control with an owner rather than a convention.
The opt-up is not assumed to be the only one, which is why it is not hardcoded.
A national-law overlay may classify investors on its own terms, and whether it carries an elective route like MiFID's is a question for that regime's text at the time it is configured. The point is that the store does not have to be changed to find out. So the store holds one classifier per classification axis, and which value is elective and which is the fallback are configured rather than compiled in — both were MiFID constants until they had to serve a second regime.
The constraint that makes several axes safe: a classifier may name only its own axis. One permitted to read a second could read an axis whose own classifier reads the first. And that cross-axis loop is the same non-terminating evaluation a single-axis version already produced once, one level less visible.
Configuring it is where this goes wrong quietly.
The nominated entry's predicate has to be evaluable at the raw elective value — the value the investor holds before the covenant is judged. A mask that excludes that value reports "does not apply" for every elective holder, the classification is believed with no record behind it, and the control fails open in exactly the direction rule 6 exists to stop. setClassifier() refuses four configurations outright: a mask excluding the elective value, a predicate naming an axis other than the one being configured, an unknown covenant, and an elective value equal to its own fallback — the last because it configures a control that can never demote anyone while looking configured.
What it cannot check is polarity: nothing on chain knows which of two values a regime considers more protective. So a fallback set to the less protective value inverts the control rather than weakening it. That one is a configuration review, not a guard.
5. Fail closed on an unevaluable predicate.
If jurisdiction is absent from the identity record, a naive predicate returns false and the covenant silently stops applying — a fail-open wearing the costume of a passing check. appliesTo() returns two booleans, applies and evaluable, and the gate treats could-not-evaluate as unsatisfied.
6. Downgrade is the dangerous direction.
Professional to retail — an opt-down, or an elective opt-up that fails re-verification — makes covenants apply that never did. An investor who has not completed the opt-up is still retail; a self-declaration in a sign-up form is not an opt-up.
Jurisdiction is a set on the covenant side and a single value on the investor's, and the asymmetry is deliberate.
A covenant may apply across several Member States, so the entry holds a set. A person holds one jurisdiction — residence. They may well have others that matter to a lawyer: tax residence, nationality. The design does not carry them, and an entry keying on them is not supported rather than partially supported. Configure one and it will match on residence, which is the wrong investor set and says nothing about being wrong. None of the six obligations above keys on jurisdiction at all. The dimension is there for national-law overlays, which are a separate per-Member-State exercise, and that is the point at which a second country becomes a change to the person record rather than a flag in the predicate.
6. Rules that must hold
Applies to every deployment of the store.
Bind the document version hash, never a boolean.
A hasAcknowledged flag passes every test you will write and breaches on the first document revision. Recording against a superseded version is rejected at write time as well as read time — it would fail the gate anyway, but failing it at the write turns a silent never-satisfied record into a legible error.
Invalidation is a read, not a sweep.
When the registry anchors a new version, outstanding entries against the old hash stop satisfying the gate by comparison at check time — never by iterating holders. A sweep is unbounded gas and, worse, a re-anchor that silently half-completes.
A covenant failure is an informative refusal, not an opaque one.
It sits on the curable side of the transfer gate's two-class rule: a missing acknowledgement applies to everyone in the same position, says nothing about anyone's conduct, and the holder fixes it by signing. Naming it is a service. What must never route through this store is a stop that could be a suspicion — the opaque class has one argument-free error and one storage location, and a covenant store raising its own named error beside it would be a second channel to read the class off.
Bound the per-gate set.
Each gate iterates its applicable entries on the hot path. An unbounded set is a gas denial-of-service and a silent ceiling change; the cap is one constant every iterating contract reads.
Adding a covenant to a live asset is a cutover, not a toggle.
The moment an entry goes live, every existing holder satisfies nothing, and the next transfer blocks the entire base. An entry carries an effective-from date: set it ahead, back-record the existing base, then let it bite. How the base is back-recorded depends on the attestor type, and only one of the two can be done for the holder. An operator-attested entry is back-recorded through recordAttestation(). An investor-signature entry cannot be: the attestor kinds are symmetric, so recordAttestation() refuses one and signCovenant() refuses the other, and the only route in is the investor's own transaction. Nobody can sign the managers' register on a director's behalf — which is the point of requiring it — so for those entries the cutover is a collection campaign with an effective-from date long enough to run it, not a back-fill.
7. The PRIIPs limb, and where document anchoring takes over
Applies where the product is packaged and any investor in the chain is retail. Professional-only distribution removes it.
PRIIPs splits the key information document into three duties owned by two contracts. Article 5 — the manufacturer draws it up before the product is made available to retail. Article 10 — it is reviewed at least every 12 months and on any material change, and revised where the review requires. Article 13 — the distributor provides it to the retail investor in good time before they are contractually bound; attaching it to the trade confirmation is a breach, not a cure.
The first two are anchoring duties. Each version has its own hash in the document registry, and a review that finds no revision needed is attested there against the current version — that contract is the subject of the document-anchoring article. Only Article 13 is a covenant: the investor's acknowledgement of a specific version, checked on the subscription path and on a distributor's sale path, and invalidated by the next revision.
The asymmetry to carry across the two articles: anchoring a revised prospectus supplement opens a statutory withdrawal window for investors who already subscribed. Anchoring a revised key information document opens none — it invalidates outstanding acknowledgements instead, and the next subscription requires a fresh one. Same governance path, different consequence, and the escrow's window machinery must not be reused for the second.
8. A store plus a predicate — not a second compliance engine
Applies to every deployment. Stated because the alternative is the natural way to write it.
Rule evaluation stays in the rule engine. The covenant store answers "is this investor's entry for this gate satisfied?" through assertSatisfied(), and a thin adapter presents that onto the engine's interface like every other gate. It does not veto transfers on its own account. Two contracts that can independently refuse a transfer for different reasons is how a reason code leaks from the module that was supposed to stay silent — the refusal now identifies which contract fired, whatever the code says.
InnBlockchain is an RWA tokenization development company building this covenant, identity and transfer-restriction architecture end to end — scope your build.
At a glance
| Rule | What it prevents |
|---|---|
| Covenants are not claims | A boolean acknowledgement that survives the document it was given against |
| No hardcoded covenants; opaque identifiers | A redeploy per regime, and a national-law overlay that cannot be paired |
| Gates are a set — mint, send, receive; a burn is send only | One enforcement policy applied to obligations that need three different ones, and a receive-scoped covenant that kills every redemption |
| Attestor kinds are symmetric — investor signature forced for the managers' register, operator attestation refused to the investor | An operator attesting to a fact only the director can know, and an investor pressing a button to satisfy a distributor's suitability check |
| Two-dimension predicate at the gate; fund-level conditions applied at configuration | A covenant switching itself off for an investor it still binds — and a fund-level condition re-derived at every transfer when the configurer had already resolved it |
| One entry per obligation and predicate | An audit map that cannot resolve an entry to one Article |
| One share class per token; a registry serves one asset | An acknowledgement given against one class's document satisfying the gate for a class the investor was never shown |
| One classifier per axis, confined to its own axis, resolved first | A self-declared professional whose retail covenants were silently switched off — and a classifier reading a foreign axis, which loops |
| Fail closed on unevaluable | A missing jurisdiction field reading as "not applicable" |
| Invalidation by read | A sweep that half-completes |
| Informative refusal, bounded set, dated cutover | A curable gap collapsed into an opaque code, a gas ceiling, and a whole base blocked at once |
This is engineering commentary on regulatory requirements, not legal advice. Which acknowledgements a specific product owes, to whom, and in which Member State needs sign-off from counsel.






