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.
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.
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
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, version1, the chain id, and the Claim Issuer's ONCHAINID as verifying contract. Read it from the identity witheip712Domain()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 ofMetadata, read withERC734Validator.getMetadataHash(scheme, uri). The function is on the module, at the addressIdentity.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
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:
The caller holds
MANAGEMENTon the issuer's identity. OtherwiseSenderDoesNotHaveManagementKey.The claim is
Validfor this issuer. OtherwiseInvalidClaim.The issuer's ONCHAINID holds a claim key on the Investor's identity. If not:
IdentityFactory.identityTypeOf(issuer)isCLAIM_ISSUER, thenReputationRegistry.reputationOf(issuer)is at leastclaimAddThreshold(). OtherwiseIdentityNotClaimIssuerTypeorReputationBelowClaimAddThreshold.The topic is not zero (
InvalidClaimTopic), and the signature, the payload and the URI are within their limits (ClaimSignatureTooLong,ClaimPayloadTooLong,ClaimUriTooLong).data.metadataHashmatches the scheme and the URI. OtherwiseClaimMetadataMismatch.isClaimValidon the issuer returns true. OtherwiseInvalidClaim.
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'saddClaimandremoveClaimare 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.
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
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
ClaimRemovedand noClaimRevoked. 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_SIGNERkey.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_ISSUERidentities, and adapters governed by Network governance. For an identity, they checkIdentityFactory.identityTypeOf(issuer).Read your status per asset.
isTrustedIssuer(issuer)andgetTrustedIssuerClaimTopics(issuer)on itsTREXRegistry, and the eventsTrustedIssuerAdded,TrustedIssuerRemovedandClaimTopicsUpdated.
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.
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_ISSUERin theIdentityFactory, with signing keys separate fromMANAGEMENT.Every claim is signed over
getClaimHash, with no personal data in the payload.validUntilis 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.