ERC-8320: Regulated Asset Claim

We propose a registry standard for verifiable claims about on-chain assets.

The gap:
Existing standards define how assets behave: ERC-4626/7540/7575 hold and settle value, ERC-3643/7943 enforce who may hold and transfer. None define what an asset is, what it is worth, or what backs it, in a form a machine can verify. That data lives in PDFs, so every integration is bespoke. Self-description doesn’t solve it: a self-reported NAV proves nothing about who stands behind it.

The primitive:
A claim: a signed, versioned, hash-anchored statement about an asset, held in a registry keyed by assetId. Eight claim types: IDENTITY, VALUATION, MANDATE, TERMS, COMPLIANCE, BACKING, EVENT, RISK. Payload off-chain; the chain holds the anchor, schema reference and hash, and content hash.

Maker-checker, on-chain:
Three roles per (assetId, claimType), granted by the registry admin: AUTHOR proposes, VALIDATOR validates or revokes, ACTIVATOR activates or suspends. Validator must differ from author. Lifecycle: PROPOSED, VALID, ACTIVE, EXPIRED (time-derived), REVOKED. Every act is an EIP-712 signature with nonce and deadline, ERC-1271 supported, so oracles and multisig committees attest off-chain and anyone submits. Segregation of duties, enforced by the contract.

Trust direction:
Multiple claims of one type may be active; validator and activator curate the set. An asset may implement IRegistryAnchor to approve the registries it recognizes, like cross-listing: same instrument, many venues, issuer picks. Unapproved registries must be ignored. Assets that cannot anchor fall back to trusting the registry admin.

Open questions:

  1. Canonical schemas: Every payload must conform to the canonical schema referenced by schemaId. Should the per-claim-type schemas (valuation, compliance, backing…) be normative inside the ERC, or a separate versioned spec the ERC references? Inside: stable and reviewable. Outside: evolves with new asset classes without touching the standard

Draft
Reference implementation

3 Likes

@thamerdridi Interesting proposal, and great to see another team contributing to this part of the standards landscape. There is meaningful but partial overlap with our existing candidate ERC family, particularly around asset identity and backing, compliance records, and valuation. That convergence reinforces that machine-readable trust, asset claims, and lifecycle data are becoming shared infrastructure problems rather than project-specific concerns.

On July 2nd, we shared a related discussion covering six specialized candidate ERC interfaces for asset binding, canonical document-bundle commitments, directional transfer domains, compliance events, impact snapshots, and NAV reporting.

The repository includes Foundry reference implementations, unit/fuzz/invariant testing, and an independent Verichains audit.

Our individual ERC drafts and PR branches are implementation-ready, but we are intentionally waiting to open them until the community discussion has tested the proposed decomposition, prior art, and potential areas of overlap. We want community feedback to be able to reshape the boundaries before those interfaces enter the formal ERC process.

We see an important architectural distinction between the approaches. ERC-8320 provides a flexible, generic signed-claim envelope whose semantics live in external schemas. Our work instead defines specialized interfaces with typed on-chain fields and domain-specific query behavior.

That specialization is deliberate. Safety-critical semantics such as binding exclusivity, canonical bundle derivation, directional route permission, correction provenance, measurement periods, methodology changes, NAV basis, currency, staleness, and provider aggregation are directly available to consuming contracts without fetching and decoding schema-specific off-chain payloads. This reduces adapter ambiguity and gives integrations deterministic query behavior across implementations.

The tradeoff is flexibility versus typed composability: a generic claim registry can evolve through new schemas without changing its interface, while specialized interfaces make important values and invariants directly queryable and, where specified, enforceable on-chain. Because the approaches overlap somewhat, we believe the author teams should coordinate scope and explore partnership before the proposals progress independently.

A few questions:

  1. Canonical schema representation: schemaHash authenticates particular schema bytes, but how are those bytes retrieved and canonicalized? Two semantically identical JSON schemas can hash differently. Does the proposal need a normative schema serialization rule or schema URI?

  2. Multiple active claims: when multiple claims of the same (assetId, claimType) are active, how should an on-chain consumer deterministically select one? This seems particularly important for VALUATION, COMPLIANCE, and BACKING, where selecting a different author or schema may produce materially different results.

  3. Typed on-chain consumption: because claim payloads remain off-chain, a consuming contract cannot directly query values such as NAV, currency, valuation timestamp, compliance outcome, or backing amount. Is ERC-8320 intended primarily as a discovery and attestation envelope that may reference specialized typed interfaces, or as a replacement for those interfaces?

  4. Asset identifier derivation: should the assetId derivation specify domain separation and exact ABI encoding, for example:

    keccak256(abi.encode("ERC-8320:ASSET", chainId, contractAddr, subAssetId))

    How should namespaces work for off-chain assets where contractAddr == address(0)?

  5. Maker-checker independence: requiring the validator address to differ from the author provides address-level separation, but does not establish organizational or beneficial independence. Would it be more precise to describe this as enforced role separation rather than enforced segregation of duties?

We will add ERC-8320 to the prior-art discussion for our candidate interfaces. Given the adjacent scope and partial overlap, would you and your co-authors be open to partnering with us on this standards work?

We would welcome a direct conversation about aligning the areas of shared scope, combining the teams’ expertise, and exploring shared authorship where the proposals address the same requirements.

4 Likes

Hey @redcoat, thanks for the detailed review. We will add your thread to our prior-art discussion as well. I will push back on the framing, because the overlap runs the other direction. Your interfaces standardize specialized data surfaces; RAC standardizes who may publish a claim, who validated it, who made it live, and whether it remains trustworthy.

  • schemaHash anchors the exact schema bytes used by the claim. It is not intended to make semantically equivalent schemas produce identical hashes. The author commits to one exact artifact, and the validator reviews and signs that artifact. The validator is not merely flipping an enum status; validation is an authorized attestation over the schema and content commitment.

  • Multiple active claims are deliberate. RAC claim types are intentionally broad financial topics. Multiple valuations, backing reports, or compliance assessments may all be valid simultaneously because they come from different authorized parties, use different methodologies, or apply at different times. The role architecture curates which claims are legitimate and active.

  • RAC is an envelope by design. Its primary consumers are discovery layers that retrieve the payload off-chain, verify it against contentHash, and inspect the on-chain signatures, lifecycle, and grant table. For on-chain consumption, a valuation surface already exists: totalAssets and convertToAssets in ERC-4626/7540/7575…, where pricing methodology was deliberately left implementation-defined. Specialized NAV interfaces therefore risk re-standardizing a layer the ecosystem intentionally kept open. RAC instead standardizes the missing trust layer around that information.

  • Good catch on identifier derivation. We are adding explicit domain separation and an issuer namespace for off-chain assets. contractAddr remains address(0) for off-chain assets, while the namespace and subAssetId prevent collisions.

  • We never claimed organizational independence. RAC enforces separation at the act level: the validator of a claim must differ from its author, and every lifecycle transition carries a distinct signature. The same organization may control multiple authorized roles, but the arrangement is visible in the grant table and event history, allowing consumers to evaluate the governance model themselves.

A couple of questions back. Have you looked at the uFund proposal (ERC-8318, https://ethereum-magicians.org/t/erc-8318-ufund-standardized-fund-metadata-lifecycle-interface/28660) The strongest overlap I see is between your NAV oracle and that proposal, not with RAC!
Similarly, for document anchoring, ERC-1643 covers document management and your Proposal 2 sits directly on top of it.

Happy to connect, sure, let’s have a brief call and discuss where the layers compose.

Implementation update: a reference implementation is now available in the official ERC repository assets, including the registry, asset-side anchor, interfaces and lifecycle tests.

https://github.com/ethereum/ERCs/tree/master/assets/erc-8320

Technical feedback on both the specification and implementation is welcome.

I read the full spec and the reference implementation, and built against it. Three things: a naming clash with your other standard, a composition point I think is the interesting one, and a small wording fix on BACKING.

1. “Mandate” means two different things across your two standards.

Here MANDATE is a ClaimType: what a fund’s capital will allocate to, authored by a manager. In ERC-8226 (Regulated Agent Mandate) a “mandate” is the delegation itself, a scoped, time-bounded, capped grant of agent authority from a principal. Same word, same authors, two meanings, and neither draft points at the other. If you expect both to sit on the same asset, an integrator hits “mandate” twice and has to work out which one you mean. A line of disambiguation, or a cross-reference, would save everyone that.

2. IRegistryAnchor is where this composes with the agent-execution layer, and I don’t think anyone has wired it yet.

An agent acting on a regulated asset is gated by its mandate caps, but the action also depends on the asset’s current published state: who may hold it now, the terms in force, what’s backing it. That’s what 8320 publishes, and IRegistryAnchor is the asset-side hook that says which registry speaks for the asset. You don’t need to decode any payload to use it, so the envelope boundary stays intact: the asset just checks that a required claim exists and is ACTIVE, both on-chain. Concretely, an asset can gate an agent action on a live BACKING claim:

// asset-side, before allowing an agent-initiated action
bytes32 assetId = registry.getAssetId(address(this), subAssetId, block.chainid);
require(isRegistryApproved(address(registry)), "registry not approved by this asset");
require(registry.getActiveClaims(assetId, ClaimType.BACKING).length > 0, "no live backing");
// getActiveClaims already filters to ACTIVE, in-window claims.
// the mandate check (caps, window, freeze) still runs separately.

Right now the reference material treats an asset as either carrying mutable executable state or anchoring a claim registry, not both. Wiring them is a small amount of glue, and it’s where the discovery envelope starts to matter at execution time without re-standardizing anything you deliberately kept off-chain. I’ve got a reference that does exactly this on one asset, fork-tested against the live registry, and I’ll post it here shortly.

One thing that falls out of it: 8320 can’t express the holder-to-right relationship on-chain, since it lives in the payload. So an asset that needs that at execution time still needs a typed source sitting next to the 8320 registry. Might be worth a note in the spec.

3. Small wording thing on BACKING, from actually wiring it up.

The table lists BACKING’s typical author as “Auditor,” and Roles says “whoever holds AUTHOR for BACKING ClaimType is its auditor.” That’s the right shape, and it’s how we wired the demo: the registry operator and the BACKING author are two different parties. In our setup one party runs the registry (admin, validator, activator) and a separate independent reviewer authors the backing claim, the one who actually reviews the reserves or the loanbook and signs it. The only nudge I’d make: that attesting party isn’t always a licensed audit firm. In our market it’s a receivables-verification agent; in the US it’s often a loan-review shop. So the cell might read “auditor or equivalent verification agent.” The grant table already carries the real capacity; this just keeps the label from reading narrower than the model.

For what it’s worth on our side: we run the registry, we don’t author the backing ourselves, and we don’t touch VALUATION or RISK at all. Those are judgments. The attestor signs the facts, the operator curates them, and the grant table shows who’s who.

Happy to detail more where the layers meet, and I’ll drop the demo in this thread.

1 Like

Demo’s up, as promised. It wires the composition from my last post: one asset that’s both the asset of a live ERC-8226 mandate and an IRegistryAnchor for an ERC-8320 registry, on Sepolia.

The registry is your reference implementation, unmodified. Its own suite passes under our pinned toolchain, and we built on top rather than reimplementing. FACTOS operates it; a separate key authors the BACKING claim, since the validator has to differ from the author anyway. The payload is synthetic, so this proves the mechanics and the composition, not any real reserve.

What happens on chain (Sepolia; drop the hashes into any explorer):

  • agent acts while the backing is live, succeeds: 0x277c4d8a4e22252110c32000d1bc0d681b1674b3036cb75639319eb102317131
  • FACTOS suspends the backing: 0xc6cb7dfba7531f4462a38ca9b6ba5b6f29ca60f87ce51609d2edd12734cdf444
  • agent retries: the mandate still returns (true, OK), but the asset reverts NoActiveBackingClaim: 0x192deb53b1a612dacac1ae3048d6e0876bbecccbb71989221002d584cbe1cbe7

The mandate never expired and was never revoked. The action stopped because the published backing was withdrawn from the 8320 registry. That’s the join: an 8226 execution gated on 8320 published state, neither contract modified.

Contracts:

  • ERC-8320 registry: 0x077c6f1f23DF776B25A48D2cdC015e15a7D3a002
  • anchored asset: 0x25B443684e240f0362d019AB666EDdFE0a93c3Ac
  • mandate registry (yours): 0xB7e7B1ca762144A135FB43F9f543Ee76B21B8583

Check it against an archive node, no explorer needed:

# backing is no longer active after the suspend
cast call 0x077c6f1f23DF776B25A48D2cdC015e15a7D3a002 \
  "getActiveClaims(bytes32,uint8)((bytes32,uint8,bytes32,bytes32,uint64,uint64,uint64,uint8,bytes32[],bytes32,address,string)[])" \
  0x7e646f4f719d1da5c21d22509ef7c11e9e20934c981c2d8341f7ac31832a5474 5 --rpc-url <archive>
# []

# two distinct parties on chain: the auditor authors, FACTOS validates and never authors
cast call 0x077c6f1f23DF776B25A48D2cdC015e15a7D3a002 "isAuthorized(bytes32,uint8,uint8,address)(bool)" \
  0x7e646f4f719d1da5c21d22509ef7c11e9e20934c981c2d8341f7ac31832a5474 5 0 \
  0x903b5f200935a650720132729FaEA8dB9174F3d3 --rpc-url <archive>
# true

Happy to send the full write-up separately; the account’s new here and the forum is stripping links.

1 Like

@JES-Factos.co Have you tested the gate with multiple active BACKING claims or a claim reporting insufficient reserves?

Suspending the relevant claim could leave an unrelated one active. A correctly validated report showing only 60% coverage would also pass the existence check.

How would the asset distinguish acceptable backing from merely having a published report when the details live off-chain?

Would this require explicit claim selection plus a schema-specific adapter? If so then which part of the execution policy becomes reusable across issuers through ERC-8320?

You’re right on both Sergeev, and neither is handled by the demo’s gate. It checks existence only: getActiveClaims(assetId, BACKING).length > 0. With two active claims, suspending one leaves the other and the gate still passes. A validly published claim reporting 60% coverage passes the same check, because the number lives in the payload and the gate never reads it. I used the trivial policy on purpose, to isolate the one thing the demo was for: an 8226 execution gated on 8320 lifecycle state, with no standard contract modified. It isn’t a production backing policy, and I should have said so.

The line I’d draw is mechanism vs policy. What 8320 makes reusable across issuers is the envelope: an authorized author published a claim, a distinct validator signed it, an activator made it live, and it is still active. That reads only the claim’s immutable author field and its lifecycle state, both on-chain, needs no schema and no adapter, and is identical for every issuer. Everything past existence is policy, and it splits in two:

  • Selection, when several claims are active. The spec already puts this on the consumer: getActiveClaims returns the set and the consumer picks. A real gate names who it trusts: require an active claim from a specific authorized author, or require every active claim to hold, or take the highest version. That reads the claim’s author and state, not the payload, so it stays reusable. The single-required-author policy is the simplest and it carries the obvious availability tradeoff, so all-must-hold or m-of-n is where you’d go if one attestor going quiet shouldn’t stall the asset.

  • Adequacy, your 60% case. That number lives off-chain by design, so reading it needs a schema-specific adapter or an off-chain consumer decision. That’s the envelope boundary drawn earlier in this thread, and it doesn’t compose across issuers without agreeing on a schema.

So, directly: the reusable part is “is there a live, authorized, curated backing attestation from an author I trust.” The adapter-bound part is “does its content clear a threshold.” 8320 standardizes the first; the second is where issuer or schema specific logic sits, and keeping them apart is the point rather than a gap.

I built the selection case rather than leave it as words: the asset now takes an active claim whose author is the auditor it requires, so a second authorized author’s claim doesn’t hold the gate open, and suspending the required one reverts even while the other stays active. Runs green in the fork test against the live registry. Happy to put the on-chain run up here if it’s useful. y in registry dot factos dot co

On-chain now, closing my last note. Same composition, but the gate selects by required author, not mere existence. Two authorized auditors each publish an active backing claim; the asset requires auditor A. I suspend A’s claim, B’s stays active, the agent retries, and the asset reverts NoActiveBackingClaim even though a backing claim is still live.

  • both claims active, agent acts: 0xd521cfd27bbed5afa8e7be75752052e772418d33f8d5d1c1e49fee14bc12ca90
  • suspend auditor A’s claim: 0xb5c1e545e35763203697d1f06d09e405845b543ab66ff7b3e0e9d35d0c6d63a3
  • retry, reverts while B’s claim is still active: 0x773f55e847b1578d4c87f946d5f1a22aadce2c656d9fc2427eebbd2dbaf95073

The claim still active after the suspend is B’s, so a length-only gate would have passed and selection didn’t. Content adequacy stays off-chain and out of scope, as before.

JES-Factos.co, thanks a lot for the work you’ve put into this. Building both cases and sharing the transactions helps us see how the proposals work together in practice. SergeevDmitry, your question about reuse is worth discussing further.

The reason multiple ACTIVE claims are allowed, is that a claim type can cover several things. Under VALUATION, for example, an issuer could publish NAV and a separate yield assessment. claimVariant distinguishes them in the payload. Even with the same schema, different authors may publish different assessments, and consumers need to choose which they rely on. Suspending one version therefore leaves the others available.

There is also a trust decision before selection: which registry does the consumer recognize? If the asset implements IRegistryAnchor, only its approved registries should be considered. Without an anchor, that trust rests directly on the registry and its administrator.

For reuse, already there is the common payload envelope. The next step is agreeing on the structure of data, so issuers using the same schema and version can share the same parsing and validation logic.

Would it be useful to define shared schemas for specific cases? We could use those to test how much consumer logic can be reused across issuers.

Thanks Thamer. The claimVariant explanation lines up with what we hit: our required-author gate is just one selection policy a consumer can pick, and it reads the claim’s author and lifecycle, nothing from the payload. And the trust-before-selection point is already in the demo: the asset approves its registry through IRegistryAnchor, and if the holder unapproves it, the action fails closed before any claim is read.

On shared schemas: yes, and I think BACKING is the case to start with. We author BACKING and not VALUATION or RISK, so it’s the one where we can put a concrete first draft on the table rather than theorize. A BACKING schema for a credit-backed instrument, a receivables pool or a note, has a small stable core: what the backing is, the hash of the evidence it points to, its jurisdiction, the date the data applies to, and whether there is an encumbrance on the right. Structure only. Whether the coverage clears a threshold stays the consumer’s call, as we agreed.

If it’s useful, I’ll bring a first candidate BACKING schema, versioned, with the envelope fields you already define plus the BACKING data block, and we can wire two issuers against it to measure exactly how much parsing and validation logic is reused. That is the cross-issuer reuse test you’re describing, made concrete.

Following up on the first BACKING schema I mentioned. Here’s a candidate for a credit-backed instrument, structure only. The core is small and jurisdiction-agnostic, jurisdiction is a field, so two issuers can populate it and one consumer parses both:

  • backingType — reserves / collateral / receivables pool / guarantee
  • instrument — { type, jurisdiction (ISO code) }
  • evidence — { contentHash, hashAlgorithm, cid }: anchors the off-chain evidence package, not a claim that it proves the right
  • coverage — { backedAmount, referenceAmount, currency }: what backs it and to what extent, as a measured fact; whether it clears a threshold stays the consumer’s call
  • encumbrance — { status, beneficiaryKnown, basis? }: whether a competing charge sits on the right; a charge whose beneficiary is unknown is recorded as such, not smoothed over
  • holderRef — a salted commitment to the recorded holder, explicitly not a certification of ownership

Two design calls worth flagging:

  • Required are backingType, instrument, evidence, encumbrance. encumbrance is required on purpose, so silence can’t pass as clean.
  • The field descriptions carry the governing law so no field reads as constituting or legitimating the right, only recording an attested fact. Recording an encumbrance does not perfect it; that follows the applicable registry or endorsement law.

One boundary I’d hold: this is a backing attestation, a measured fact from an independent verifier, not a valuation. The schema carries what backs the instrument and the evidence for it, not what the instrument is worth.

An issuer can populate every field from state it already holds, so it’s a real output rather than a paper design. The claim commits to the exact schema file through schemaHash.

If it’s useful I’ll bring the full JSON Schema and a worked example, and we can wire a second issuer against it to measure the reuse you described.

Picking up the claimType-by-claimType idea, here is a concrete BACKING schema to react to. It is scoped to credit-backed instruments (a promissory note, a receivables pool, a loan), it is a draft to put on the table rather than a finished thing, and it is CC0.

The one line that governs it: structure, not adequacy. The schema standardizes how a backing attestation is expressed, so any consumer parses it the same way. It does not decide whether the backing is enough. coverage carries the numbers; whether they clear a threshold is the consumer’s call. That is the two-layer split from the thread: the envelope and the field structure are reusable across every issuer, while the adequacy decision is issuer- and policy-specific and stays with the reader. A coverage figure is a measurement, not investment advice. Backing, not valuation.

Who attests vs. who records. A BACKING data block is meaningless without the attestor named in the envelope. For credit backings that attestor is an independent verification agent, distinct from the registry operator. The registry records the attestation; it does not attest adequacy or value, so the schema carries no attestor-identity field. The envelope already does, through attestorProfile and the on-chain signer.

Jurisdiction is a field, not baked in. instrument.jurisdiction is an ISO 3166-1 country code, so the same schema serves a receivables pool under one law and a note under another. The field descriptions carry the governing law so no field can be read as constituting or legitimating the right (the examples happen to be Colombian): recording a holder is not legitimation, and recording an encumbrance is not perfecting it.

The honest edge case. An encumbrance whose beneficiary is unknown is recorded as such (status, beneficiaryKnown), not smoothed over. A consumer reading status != NONE or beneficiaryKnown == false should treat the backing as not clean. The registry publishes the fact of the lock and stops, instead of inventing who holds the key.

The data schema (JSON Schema 2020-12):

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://registry.factos.co/schemas/erc8320/backing.credit.v1.json",
  "title": "ERC-8320 BACKING data — credit-backed instrument (v1, candidate)",
  "description": "Candidate structure for the `data` object of an ERC-8320 claim of type BACKING, for a credit-backed instrument such as a promissory note (pagaré) or a receivables pool. Structure only: it standardizes how a backing attestation is expressed so any issuer's payload parses and validates the same way. It does NOT encode adequacy; whether the coverage clears a threshold is the consumer's decision. It records facts AS ATTESTED by the party named in the ERC-8320 envelope; it does not certify that a right is valid or enforceable, nor legitimación cambiaria, nor the authenticity of who holds it. A `data` block is meaningless without that attestor: for credit backings the attestor is an independent verification agent, distinct from the registry operator, and the schema deliberately does not carry attestor identity because the envelope does.",
  "type": "object",
  "required": ["backingType", "instrument", "evidence", "encumbrance"],
  "additionalProperties": false,
  "properties": {
    "backingType": {
      "description": "What kind of backing is asserted to stand behind the instrument.",
      "type": "string",
      "enum": ["RESERVES", "COLLATERAL", "RECEIVABLES_POOL", "GUARANTEE", "OTHER"]
    },
    "instrument": {
      "description": "The credit instrument being backed.",
      "type": "object",
      "required": ["type", "jurisdiction"],
      "additionalProperties": false,
      "properties": {
        "type": {
          "type": "string",
          "enum": ["PROMISSORY_NOTE", "RECEIVABLES", "LOAN", "BOND", "OTHER"]
        },
        "jurisdiction": {
          "description": "Governing jurisdiction of the instrument, ISO 3166-1 alpha-2.",
          "type": "string",
          "pattern": "^[A-Z]{2}$"
        }
      }
    },
    "evidence": {
      "description": "Anchor to the off-chain evidence package that substantiates the backing. The hash binds this claim to that exact package; it is not a claim that the evidence proves the right.",
      "type": "object",
      "required": ["contentHash", "hashAlgorithm"],
      "additionalProperties": false,
      "properties": {
        "contentHash": {
          "description": "Hash of the evidence package, 0x-prefixed 32-byte hex.",
          "type": "string",
          "pattern": "^0x[0-9a-fA-F]{64}$"
        },
        "hashAlgorithm": {
          "description": "Which hash produced contentHash. FACTOS uses SHA-256.",
          "type": "string",
          "enum": ["SHA-256", "KECCAK-256"]
        },
        "cid": {
          "description": "Content-addressed locator (e.g., an IPFS CID) for retrieval.",
          "type": "string"
        },
        "packageType": { "type": "string" },
        "uri": { "type": "string" }
      }
    },
    "coverage": {
      "description": "The coverage figure, expressed as structure so any consumer parses it the same way. The schema carries the numbers; it does NOT decide whether they are adequate. The figures are asserted by the attestor named in the ERC-8320 envelope, an independent verification agent for credit backings, not the registry operator; FACTOS operates the registry and does not attest adequacy or value. A coverage figure is a measurement, not investment advice; whether it is adequate is the reader's decision. Amounts are decimal strings to avoid floating-point loss.",
      "type": "object",
      "required": ["backedAmount", "referenceAmount", "currency"],
      "additionalProperties": false,
      "properties": {
        "backedAmount": { "type": "string", "pattern": "^[0-9]+(\\.[0-9]+)?$" },
        "referenceAmount": { "type": "string", "pattern": "^[0-9]+(\\.[0-9]+)?$" },
        "currency": {
          "description": "ISO 4217 currency code.",
          "type": "string",
          "pattern": "^[A-Z]{3}$"
        }
      }
    },
    "encumbrance": {
      "description": "Whether a competing charge sits on the right, as attested. Recording an encumbrance status is a record of an attested fact; it does not constitute, perfect, or make the charge opposable to third parties. Opposability follows the applicable law, for example registration of a security interest in the collateral registry (in Colombia, the RGMC under Ley 1676 de 2013) or, for a título valor, the endoso en garantía (C.Co. art. 659). An encumbrance whose beneficiary is unknown is recorded as such, not smoothed over; a consumer reading `status != NONE` or `beneficiaryKnown == false` should treat the backing as not clean.",
      "type": "object",
      "required": ["status", "beneficiaryKnown"],
      "additionalProperties": false,
      "properties": {
        "status": {
          "type": "string",
          "enum": ["NONE", "PLEDGED", "SEIZED", "UNDER_DISPUTE", "UNKNOWN"]
        },
        "beneficiaryKnown": { "type": "boolean" },
        "basis": {
          "description": "Legal source of the charge, when known. Keeps the status jurisdiction-agnostic while letting the source be stated. SECURITY_INTEREST = registered security interest (e.g., garantía mobiliaria); PLEDGE_ENDORSEMENT = endoso en garantía on a título valor; JUDICIAL_MEASURE = court measure such as an embargo; DISPUTE = pending litigation.",
          "type": "string",
          "enum": ["SECURITY_INTEREST", "PLEDGE_ENDORSEMENT", "JUDICIAL_MEASURE", "DISPUTE", "OTHER"]
        },
        "ref": {
          "description": "Optional 0x-prefixed 32-byte hex reference to an off-chain record of the charge.",
          "type": "string",
          "pattern": "^0x[0-9a-fA-F]{64}$"
        }
      }
    },
    "holderRef": {
      "description": "Identifier of the party the registry has RECORDED as holder of the right. This is not legitimación cambiaria and does not substitute the instrument's law of circulation: for a pagaré a la orden, possession plus an unbroken chain of endorsements (C.Co. arts. 647, 661); for a nominative instrument, entry in the issuer's register plus the document text (C.Co. art. 648). Not a certification of ownership or identity. The value MUST be a salted, non-reversible commitment: a bare hash of a national ID such as a Colombian cédula is brute-forceable and remains personal data under Ley 1581 de 2012, so publish only a salted commitment.",
      "type": "object",
      "required": ["recordedHolderHash"],
      "additionalProperties": false,
      "properties": {
        "recordedHolderHash": {
          "description": "Salted, non-reversible commitment to the recorded holder. 0x-prefixed 32-byte hex.",
          "type": "string",
          "pattern": "^0x[0-9a-fA-F]{64}$"
        }
      }
    }
  }
}

A synthetic example payload (the ERC-8320 envelope wrapping a conforming data block):

{
  "schemaId": "factos:backing.credit.v1",
  "schemaVersion": "1",
  "claimVariant": "backing.credit",
  "assetId": "0x7e646f4f719d1da5c21d22509ef7c11e9e20934c981c2d8341f7ac31832a5474",
  "claimVersion": 1,
  "attestorProfile": "independent receivables-verification agent, not the registry operator",
  "authoredAt": "2026-09-21T14:00:00Z",
  "dataAppliesAt": "2026-09-20T00:00:00Z",
  "accessClassification": "PERMISSIONED_DISCLOSURE",
  "data": {
    "backingType": "RECEIVABLES_POOL",
    "instrument": { "type": "RECEIVABLES", "jurisdiction": "CO" },
    "evidence": {
      "contentHash": "0x1a3f9c2b7d4e5061728394a5b6c7d8e9f0112233445566778899aabbccddeeff",
      "hashAlgorithm": "SHA-256",
      "cid": "bafybeigdyrsyntheticexampleonly",
      "packageType": "FACTOS-evidence-pack",
      "uri": "ipfs://bafybeigdyrsyntheticexampleonly"
    },
    "coverage": { "backedAmount": "1200000000", "referenceAmount": "1000000000", "currency": "COP" },
    "encumbrance": { "status": "NONE", "beneficiaryKnown": true },
    "holderRef": { "recordedHolderHash": "0x9b2c4e6a8d0f1234567890abcdef1234567890abcdef1234567890abcdef1234" }
  }
}

schemaHash = 0x0b88814b5dc3c18e868259d0dfcc701a4fa2d67e9792210e42615fc138a21d48, keccak256 over the exact bytes of the schema file, so a claim commits to one exact artifact and semantically equivalent but differently serialized files do not collide. The exact bytes are served so anyone can fetch and reproduce the hash: https://registry.factos.co/schemas/erc8320/backing.credit.v1.json (the schema’s $id), also at an immutable hash-addressed path under /schemas/erc8320/by-hash/.

The reuse test this sets up, which is the question you raised: two issuers populate backing.credit.v1 for two different instruments under two jurisdictions, and a single consumer parses both with the same code (resolve the payload, verify contentHash, read encumbrance and coverage, apply its own threshold). Everything up to the threshold is reused; only the threshold the consumer chose differs. That measures, concretely, how much consumer logic ERC-8320 makes reusable across issuers.

Open, for discussion:

  • Is this the right small stable core, or is anything missing or over-specified?
  • jurisdiction as an ISO code keeps it issuer-agnostic. Is a finer legal-basis field wanted, or does encumbrance.basis already cover it?
  • Should coverage be optional, since some backings are not expressed as a ratio?

Thanks for putting a concrete schema on the table, it makes the reuse question much easier to reason about.

On your third question (should coverage be optional): I’d lean toward making it conditionally required rather than simply optional. For RESERVES, COLLATERAL and RECEIVABLES_POOL a consumer will almost always want the numbers, and letting them be silently absent weakens the same “silence can’t pass as clean” principle you applied to encumbrance. For GUARANTEE, the backing is often a cap or a commitment rather than a measured ratio, so requiring coverage there would push issuers into filling in misleading figures. JSON Schema 2020-12 can express this directly with an if/then on backingType, so consumers still get a single schema to validate against.

One boundary question that follows from “backing, not valuation”: coverage assumes backedAmount and referenceAmount share one currency. When collateral is denominated differently from the instrument, someone has to convert it, and that conversion is arguably a valuation judgment. Would you expect the attestor to report both amounts in their native currencies and leave conversion to the consumer, or is a single converted figure acceptable as long as the attestor is named in the envelope?

Thanks, both points sharpen it.

On coverage: agreed, conditionally required rather than optional. Letting it be silently absent would break the same “silence can’t pass as clean” rule we apply to encumbrance. An if/then on backingType (required for RESERVES, COLLATERAL and RECEIVABLES_POOL, not for GUARANTEE) keeps a single schema to validate against. GUARANTEE may eventually want its own shape, a cap rather than a ratio, but I’d treat that as a follow-up rather than force it into coverage.

On currency: report both amounts in their native currencies and leave conversion to the consumer. I’d drop the single shared currency so the schema carries referenceCurrency and backedCurrency separately, with no slot for a pre-converted figure. Applying a published FX rate is mechanical in itself, but choosing the rate, the timestamp and the haircut for FX risk is an adequacy decision, and adequacy is already the consumer’s call under this schema. Native amounts keep the attestor on facts and leave that judgment to the reader.

That points to a sharper version of the same boundary, which I think is the next thing to pin down: what is backedAmount measured on? For a receivables pool, a nominal outstanding balance from the ledger is a fact; an appraised or marked-to-market figure is a judgment, and the schema can’t tell them apart today. One option that reuses what ERC-8320 already has: a small basis field (NOMINAL / APPRAISED), where APPRAISED must reference a VALUATION claim authored by whoever did the appraisal. The number would then always be signed by the party accountable for it, and BACKING would never carry a valuation under the attestor’s name. Curious whether you and @thamerdridi see that as in scope for this claimType or better left per-issuer.