For the complete documentation index, see llms.txt. This page is also available as Markdown.

Integrate as a Claim Issuer

A Claim Issuer verifies participants off-chain and attests the result as claims on their ONCHAINID. This page is for a team that already runs verifications (identity, accreditation, residency) and wants them to count for assets on T-REX: what to obtain, what to sign, how to deliver it, and what to run around it. The operations themselves are in 05 Issue a claim to an investor and 19 Revoke a claim.

Element
Where it lives

The verification

Off-chain, in the Claim Issuer's own process. Nothing of it is on-chain.

The Claim Issuer

An ONCHAINID of type CLAIM_ISSUER. Its address is the issuer of its claims.

A claim

Stored on the Investor's ONCHAINID. It serves every address of the identity.

Validity

Answered by the issuer's identity each time it is asked. No verdict is stored.

Trust

Per asset: each Issuer lists the Claim Issuers it trusts, per topic.

Claims feed eligibility: who may hold. Compliance, the rules on movements, is a separate gate. One shipped rule reuses the eligibility check: SpenderVerificationModule requires the spender of a transferFrom to be eligible.

Get the identity and its keys

A CLAIM_ISSUER identity cannot be self-deployed: IdentityFactory.createIdentity reverts with IdentityTypeNotSelfDeployable. A party that holds the creation role of the type, granted by Network governance, calls IdentityFactory.createIdentityFor(account, identityType, salt, keys) with type 5: see 02 Deploy an ONCHAINID.

account is an address the Claim Issuer controls: an externally owned account or a smart-contract wallet. It is linked as the first wallet of the identity. Every key passed must be a key of account (KeyNotForAccount), and account ends up as the only MANAGEMENT key (AccountNotSoleManagementKey). The party that deployed the identity holds no key on it.

The Claim Issuer then adds its other keys: 22 Add and remove keys on an ONCHAINID.

Purpose
A Claim Issuer uses it for
Signer

MANAGEMENT (1)

addClaimTo, revokeClaimByDigest, adding and removing keys

For the first two, an address that calls the identity directly: an externally owned account or a smart-contract wallet

CLAIM_SIGNER (3)

Signing claims

Any supported signer: an externally owned account, an ERC-1271 contract, or a key behind an ERC-7913 verifier (passkey, RSA)

ACTION (2)

Ordering addClaim and removeClaim on an Investor's identity from the issuer's ONCHAINID

An address that calls execute on the identity, or any supported signer in a user operation

What to build: dedicated CLAIM_SIGNER keys for the signing service, and no claim signed with a MANAGEMENT key. That key satisfies every purpose check, so removing CLAIM_SIGNER from it withdraws nothing. An ACTION key cannot sign a claim, but it can have the issuer's identity remove, and so revoke, any claim of the issuer: hold it as tightly as MANAGEMENT.

The anatomy of a claim

Field
Content
Rule

topic

The fact attested, as a number

Not zero

scheme, uri

How the claim is read, and where supporting material is

Bound to the signature through metadataHash

issuer

The ONCHAINID of the Claim Issuer

signature

The signer and its signature, in one blob

data.issuedAt

The time of signing, in seconds

Not zero

data.validUntil

The time after which the claim expires

Zero for no expiry

data.metadataHash

The hash of scheme and uri

Must match the values submitted

data.payload

The content for the topic: a hash or an encoded value, never raw personal data

data is the struct Structs.ClaimData: issuedAt and validUntil as uint256, metadataHash as bytes32, payload as bytes, in this order. The signature, the payload and the URI are limited to 2,048 bytes each.

A claim is stored under claimId = keccak256(abi.encode(issuer, topic)). An identity holds one claim per issuer and topic, and adding another replaces it. The model is described in Claims: issuance, validity, revocation.

Sign a claim

The signing key signs the ERC-712 digest returned by getClaimHash(identity, topic, data), read on the Claim Issuer's ONCHAINID, with identity the Investor's ONCHAINID.

  • Domain: name OnchainID, version 1, the chain id, and the Claim Issuer's ONCHAINID as verifying contract. Read it from the identity with eip712Domain() instead of hardcoding it.

  • Types: Claim(uint256 topic,address subject,ClaimData data), ClaimData(uint256 issuedAt,uint256 validUntil,Metadata metadata,bytes payload), Metadata(uint256 scheme,string uri).

  • metadataHash: the hash of Metadata, read with ERC734Validator.getMetadataHash(scheme, uri). The function is on the module, at the address Identity.registryModule() returns. The identity does not expose it.

The signature field is abi.encode(bytes signer, bytes rawSignature). keccak256(signer) is the key hash that must hold CLAIM_SIGNER.

Signing key

signer

rawSignature

Externally owned account

The 20 bytes of the address

abi.encodePacked(r, s, v)

Smart-contract wallet

The 20 bytes of the address

What its ERC-1271 check accepts for the digest

Passkey, RSA key

The verifier address, then the public key

What the verifier accepts for the digest

The digest binds the claim to its subject, its issuer and its chain. It does not include the signer: the same data signed by another key has the same digest.

Deliver a claim

Way
Call
Applies when

The Claim Issuer adds it, with standing

addClaimTo(topic, scheme, signature, data, uri, identity) on the Claim Issuer's ONCHAINID

The Claim Issuer has standing: type CLAIM_ISSUER and a reputation at or above the claim-add threshold. The Investor does not transact.

The Claim Issuer adds it, with a key grant

The same call

The Investor's identity gave the issuer's ONCHAINID a CLAIM_SIGNER or CLAIM_ADDER key. Standing is not consulted.

The Identity Owner adds it

addClaim(topic, scheme, issuer, signature, data, uri) on the Investor's ONCHAINID

Any other case. The Claim Issuer hands the signed claim to the Investor, and a claim key of its identity submits it.

addClaimTo checks, in this order:

  1. The caller holds MANAGEMENT on the issuer's identity. Otherwise SenderDoesNotHaveManagementKey.

  2. The claim is Valid for this issuer. Otherwise InvalidClaim.

  3. The issuer's ONCHAINID holds a claim key on the Investor's identity. If not: IdentityFactory.identityTypeOf(issuer) is CLAIM_ISSUER, then ReputationRegistry.reputationOf(issuer) is at least claimAddThreshold(). Otherwise IdentityNotClaimIssuerType or ReputationBelowClaimAddThreshold.

  4. The topic is not zero (InvalidClaimTopic), and the signature, the payload and the URI are within their limits (ClaimSignatureTooLong, ClaimPayloadTooLong, ClaimUriTooLong).

  5. data.metadataHash matches the scheme and the URI. Otherwise ClaimMetadataMismatch.

  6. isClaimValid on the issuer returns true. Otherwise InvalidClaim.

addClaim by the Identity Owner checks that the caller holds a claim key on the Investor's identity (SenderDoesNotHaveClaimSignerKey), then 4 to 6.

Direct calls only. addClaimTo, revokeClaimByDigest, and the Identity Owner's addClaim and removeClaim are called on the identity by the key holder itself. A call relayed as a user operation, or dispatched by the identity to itself, carries no key holder and is refused.

The issuer's identity can also be the caller. A key that holds ACTION or MANAGEMENT on it calls execute(to, value, data) on the issuer's ONCHAINID, with the Investor's identity as to and the encoded addClaim or removeClaim call as data, or sends the same call in a user operation. Checks 3 to 6 apply. execute does not revert when the inner call fails: the queue emits ExecutionFailed in place of Executed, so a batch process reads the event and does not rely on a revert.

How a claim is evaluated

At every eligibility check, TREXRegistry.isVerified(address) takes each required topic and each Claim Issuer trusted for it, reads the claim stored under that issuer's claimId on the identity, and calls isClaimValid(identity, topic, signature, data) on the issuer. One valid claim per required topic is enough.

The issuer's identity decides in this order. The first condition met gives the status.

Condition
Status

issuedAt is zero

BadSignature

The block time is before issuedAt

NotYetValid

validUntil is not zero and the block time is after it

Expired

The digest is marked revoked on the issuer

Revoked

The signature blob is shorter than 128 bytes, or its signer shorter than 20

BadSignature

The signer does not hold CLAIM_SIGNER on the issuer's identity

NotIssued

The signature does not verify against the digest

BadSignature

None of the above

Valid

isClaimValid returns true for Valid only. getClaimStatus(identity, topic, signature, data) returns the status. It is a function of the ERC734Validator that takes the issuer from the caller, and the reference module set of an identity routes isClaimValid, not getClaimStatus. Read it with an eth_call to the module, with from set to the Claim Issuer's ONCHAINID.

When a key loses CLAIM_SIGNER, every claim it signed reads NotIssued from the next check. The status is computed at each call: giving the purpose back to the same key makes those claims Valid again. A revocation and an expiry are final.

Renew and revoke

Need
What to do

End on a known date

Set validUntil. Expiry needs no transaction.

Renew

Sign new ClaimData, with a new issuedAt and validUntil, and deliver it. It replaces the stored claim.

Withdraw one claim

revokeClaimByDigest(digest) on the Claim Issuer's ONCHAINID, by a MANAGEMENT key. It reverts with ClaimAlreadyRevoked for a digest already revoked, and does not check that a claim exists for the digest. isDigestRevoked(digest) reads the state.

Delete the claim from the identity

removeClaim(claimId) on the Investor's identity, called by the issuer's ONCHAINID. Accepted for its own claims with standing, or under a CLAIM_SIGNER key granted by the Investor's identity. The digest is marked revoked. Optional.

  • A revoked digest stays revoked. No function reverses it. To attest the fact again, sign new ClaimData.

  • A removal by the Identity Owner spends the digest too. The digest of the removed claim is marked revoked on the issuer, with ClaimRemoved and no ClaimRevoked. The same signed claim cannot be delivered again.

  • Revoke what you supersede. A replaced claim that is neither expired nor revoked is still valid, and the Identity Owner can add it back.

Reputation and standing

ReputationRegistry stores a score per identity (setReputation), a default score per identity type (setDefault) and one claim-add threshold (setClaimAddThreshold). All three are set by the REPUTATION_MANAGER role, under Network governance. reputationOf(identity) returns the score of the identity if one was set, zero included, and otherwise the default of its type as the factory recorded it.

A Claim Issuer has standing when reputationOf(issuer) is at least claimAddThreshold() and the factory recorded its identity as CLAIM_ISSUER. See Claim issuers, trust and reputation.

  • It gates adding the issuer's own claims to an identity where it holds no claim key, and removing them from an identity where it holds no CLAIM_SIGNER key.

  • It does not gate signing, the validity of claims already issued, delivery by the Identity Owner or under a key grant, or revocation by digest. It decides eligibility for no asset.

What to build: read both values before choosing a delivery route, and follow ReputationSet, DefaultSet and ClaimAddThresholdSet.

Be counted by an asset

A claim counts for an asset once its Issuer has added the Claim Issuer's ONCHAINID, not a signing key, with TREXRegistry.addTrustedIssuer(trustedIssuer, claimTopics): 04 Add a trusted Claim Issuer.

  • Topics are agreed with each Issuer. A topic is a number. The registry checks that a valid claim exists for it, never what the payload says.

  • Issuers are advised to trust only factory-created CLAIM_ISSUER identities, and adapters governed by Network governance. For an identity, they check IdentityFactory.identityTypeOf(issuer).

  • Read your status per asset. isTrustedIssuer(issuer) and getTrustedIssuerClaimTopics(issuer) on its TREXRegistry, and the events TrustedIssuerAdded, TrustedIssuerRemoved and ClaimTopicsUpdated.

Operations

Rotate a signing key. Add the new CLAIM_SIGNER key, sign and deliver again every claim of the old key that is still in force, then remove CLAIM_SIGNER from the old key. For a compromised key, remove the purpose first: one transaction withdraws everything it signed, forged claims included. Then issue again.

Store off-chain, per claim: the Investor's ONCHAINID, the topic, the scheme, the URI, the exact ClaimData, the signature, the signing key, the digest and the verification record. Revocation takes the digest, and only the exact data recomputes it.

Index. The shared ERC734Validator emits these events for every identity. Filter on the indexed fields, in bold.

Event
Arguments
Meaning

ClaimAdded, ClaimChanged, ClaimRemoved

identity, claimId, topic, scheme, issuer, signature, data, uri, caller

A claim was added to an identity, replaced, or deleted from it

ClaimRevoked

digest, issuer, caller

The issuer revoked a digest

ClaimAddedTo

identity, topic, signature, data, caller

addClaimTo succeeded. It does not name the issuer: pair it with ClaimAdded

KeyAdded

account, keyHash, purpose, keyType, signerData, clientData, caller

A key gained a purpose on the identity account

KeyRemoved

account, keyHash, purpose, caller

A key lost a purpose

An Investor with no identity. Read IdentityFactory.getIdentityIncludingRevoked(account), with account the wallet as an ERC-7930 interoperable address. If the wallet was never bound, a Claim Issuer that holds the creation role of the Investor's type creates the identity with createIdentityFor, with the wallet as its only manager: 02.

Attesters already on EAS. An attester on the Ethereum Attestation Service needs no ONCHAINID. The EASClaimIssuer adapter is the issuer of the claim and answers isClaimValid from EAS at every check. The Identity Owner adds the record, and Issuers trust the adapter: 29 Bring an EAS attestation in as a claim.

Checklist

  • The identity is of type CLAIM_ISSUER in the IdentityFactory, with signing keys separate from MANAGEMENT.

  • Every claim is signed over getClaimHash, with no personal data in the payload.

  • validUntil is set wherever the verification has to be renewed.

  • The delivery route follows standing, with delivery by the Identity Owner as fallback.

  • The digest, the exact data and the signing key of every claim are stored.

  • Superseded and withdrawn claims are revoked by digest.

  • Claim, key, reputation and trusted-issuer events are indexed.