TokenClaimResolverV7
Overview
The TokenClaimResolverV7 is an EAS (Ethereum Attestation Service) resolver that validates token claim attestations before they’re created. This is a critical component that enforces document ownership and prevents unauthorized claim capabilities.
What It Does
When someone tries to create an attestation that grants a user permission to claim a token, the TokenClaimResolver:
- Validates the attester is authorized (must be document owner or authorized executor)
- Verifies the document exists in the registry
- Ensures the capability is correct (must be CAPABILITY_CLAIM_TOKEN)
- Rejects invalid attestations before they enter the system
Why It Matters
Security: Prevents unauthorized users from creating claim attestations for documents they don’t own.
Gas Efficiency: Validation happens once (when creating the attestation), not on every claim attempt. This saves 30-50% gas on claim transactions.
Clean System: Invalid attestations never get created, keeping the attestation graph clean.
How Token Claims Work
Traditional Flow (Without Resolver)
1. Owner creates attestation (no validation)
2. User tries to claim with attestation
3. Tokenizer validates attestation (expensive gas cost)
4. If invalid, claim fails (wasted gas)TokenClaimResolver Flow (V7)
1. Owner tries to create attestation
2. TokenClaimResolver validates BEFORE creation
3. If invalid, attestation creation fails immediately
4. Only valid attestations exist in the system
5. User claims with pre-validated attestation (cheaper)
6. Tokenizer trusts the attestation (minimal validation)Attestation Data Format
When creating a claim attestation, the data field must be encoded as:
abi.encode(integraHash, tokenId, claimCapability)Parameters:
integraHash(bytes32): Document identifier from the document registrytokenId(uint256): Token ID being claimed (use 0 for fungible tokens or if not applicable)claimCapability(uint256): Must be1 << 1(CAPABILITY_CLAIM_TOKEN constant)
Creating Claim Attestations
Prerequisites
You must be either:
- The document owner, OR
- An authorized executor for the document
Using EAS to Create Attestations
// 1. Get the EAS contract and schema UID
IEAS eas = IEAS(easAddress);
bytes32 schemaUID = 0x...; // Schema with TokenClaimResolver
// 2. Prepare attestation data
bytes32 integraHash = 0x...; // Your document hash
uint256 tokenId = 1; // Token ID to claim
uint256 claimCap = 1 << 1; // CAPABILITY_CLAIM_TOKEN
bytes memory attestationData = abi.encode(
integraHash,
tokenId,
claimCap
);
// 3. Create attestation request
AttestationRequest memory request = AttestationRequest({
schema: schemaUID,
data: AttestationRequestData({
recipient: userAddress, // Who can claim
expirationTime: 0, // No expiration
revocable: true, // Can be revoked
refUID: bytes32(0), // No reference
data: attestationData, // Encoded data
value: 0 // No payment
})
});
// 4. Create the attestation
bytes32 attestationUID = eas.attest(request);Example: Reserve and Attest Pattern
contract DocumentOwnerContract {
IEAS public immutable eas;
IntegraDocumentRegistryV7_Immutable public immutable documentRegistry;
IDocumentTokenizerV7 public immutable tokenizer;
bytes32 public immutable claimSchemaUID;
function reserveAndAttest(
bytes32 integraHash,
uint256 tokenId,
address recipient
) external {
// 1. Verify caller is document owner
require(
documentRegistry.getDocumentOwner(integraHash) == msg.sender,
"Not document owner"
);
// 2. Reserve token in tokenizer
tokenizer.reserveToken(
integraHash,
tokenId,
recipient,
1, // amount
bytes32(0) // processHash
);
// 3. Create claim attestation
bytes memory attestationData = abi.encode(
integraHash,
tokenId,
1 << 1 // CAPABILITY_CLAIM_TOKEN
);
AttestationRequest memory request = AttestationRequest({
schema: claimSchemaUID,
data: AttestationRequestData({
recipient: recipient,
expirationTime: 0,
revocable: true,
refUID: bytes32(0),
data: attestationData,
value: 0
})
);
bytes32 uid = eas.attest(request);
emit ClaimAttestationCreated(integraHash, tokenId, recipient, uid);
}
}Validation Rules
On Attestation Creation (onAttest)
The resolver validates:
- ✅ No payment sent -
valuemust be 0 - ✅ Valid data format - Data decodes correctly to (bytes32, uint256, uint256)
- ✅ Document exists -
integraHashexists in document registry - ✅ Correct capability -
claimCapabilityequals1 << 1 - ✅ Authorized attester - Attester is owner OR authorized executor
If any check fails, attestation creation is rejected.
On Attestation Revocation (onRevoke)
The resolver allows revocation if:
- ✅ No payment sent -
valuemust be 0 - ✅ Document exists -
integraHashexists in document registry - ✅ Authorized revoker - Revoker is EITHER:
- Document owner (can always revoke), OR
- Original attester (can revoke own attestations)
Common Use Cases
Use Case 1: Real Estate Transfer
// Seller (document owner) grants buyer permission to claim deed NFT
// 1. Reserve the deed token for buyer
ownershipTokenizer.reserveToken(
deedHash,
1, // tokenId
buyerAddress,
1,
bytes32(0)
);
// 2. Create claim attestation (validated by TokenClaimResolver)
bytes32 uid = eas.attest(AttestationRequest({
schema: claimSchemaUID,
data: AttestationRequestData({
recipient: buyerAddress,
expirationTime: block.timestamp + 30 days, // 30 day deadline
revocable: true,
refUID: bytes32(0),
data: abi.encode(deedHash, 1, 1 << 1),
value: 0
})
}));
// 3. Buyer can now claim the deed token
// (using the attestationUID)Use Case 2: Multi-Party Agreement
// Company grants multiple stakeholders claim rights
address[] memory stakeholders = [cfo, cto, investor];
uint256[] memory tokenIds = [1, 2, 3];
for (uint256 i = 0; i < stakeholders.length; i++) {
// Reserve token
multiPartyTokenizer.reserveToken(
agreementHash,
tokenIds[i],
stakeholders[i],
1,
workflowHash
);
// Create claim attestation
eas.attest(AttestationRequest({
schema: claimSchemaUID,
data: AttestationRequestData({
recipient: stakeholders[i],
expirationTime: 0,
revocable: true,
refUID: bytes32(0),
data: abi.encode(agreementHash, tokenIds[i], 1 << 1),
value: 0
})
}));
}Use Case 3: Conditional Claims with Executor
// Use executor pattern for programmatic claim attestation
// 1. Document owner authorizes an executor contract
documentRegistry.authorizeDocumentExecutor(
integraHash,
address(executorContract)
);
// 2. Executor can create claim attestations based on business logic
contract SmartExecutor {
function grantClaimOnPayment(
bytes32 integraHash,
uint256 tokenId,
address recipient
) external payable {
require(msg.value >= requiredPayment, "Insufficient payment");
// Executor creates claim attestation
eas.attest(AttestationRequest({
schema: claimSchemaUID,
data: AttestationRequestData({
recipient: recipient,
expirationTime: 0,
revocable: true,
refUID: bytes32(0),
data: abi.encode(integraHash, tokenId, 1 << 1),
value: 0
})
}));
// Forward payment to document owner
payable(documentRegistry.getDocumentOwner(integraHash)).transfer(msg.value);
}
}Error Handling
Common Errors
UnauthorizedAttester
Cause: Attester is not document owner or authorized executor
Solution: Ensure you're the document owner or get authorizationInvalidCapability
Cause: Capability in attestation data is not 1 << 1
Solution: Use correct capability constant: abi.encode(hash, tokenId, 1 << 1)DocumentNotFound
Cause: integraHash doesn't exist in document registry
Solution: Register the document firstInvalidAttestationData
Cause: Attestation data doesn't decode correctly
Solution: Ensure data is abi.encode(bytes32, uint256, uint256)Checking Authorization Before Attesting
// Check if you can create attestations before attempting
TokenClaimResolver resolver = TokenClaimResolver(resolverAddress);
bool canAttest = resolver.isAuthorizedAttester(
integraHash,
msg.sender
);
if (!canAttest) {
revert("Not authorized to create claim attestations");
}Revoking Claim Attestations
Who Can Revoke
- Document Owner: Can revoke any claim attestation for their document
- Original Attester: Can revoke their own attestations
Revocation Example
// Revoke a claim attestation
eas.revoke(RevocationRequest({
schema: claimSchemaUID,
data: RevocationRequestData({
uid: attestationUID,
value: 0
})
}));
// After revocation, the user can no longer claim the tokenIntegration with Tokenizers
All V7 tokenizers use the requiresCapabilityWithUID modifier which:
- Validates the attestation exists and is valid
- Checks the attestation’s capability matches the required capability
- Verifies the attestation recipient is msg.sender
- Ensures the attestation hasn’t been revoked
Because TokenClaimResolver validates attestations upfront, tokenizers can trust them.
// Inside a tokenizer's claimToken function:
function claimToken(
bytes32 integraHash,
uint256 tokenId,
bytes32 capabilityAttestationUID,
bytes32 processHash
) external
requiresCapabilityWithUID(
integraHash,
CAPABILITY_CLAIM_TOKEN, // 1 << 1
capabilityAttestationUID
)
nonReentrant
{
// Attestation is already validated
// Proceed with claim logic
_mint(msg.sender, tokenId);
}Schema Setup
Required EAS Schema
When deploying TokenClaimResolver, you need to register an EAS schema:
Schema Definition:
bytes32 integraHash,uint256 tokenId,uint256 claimCapabilitySchema Registration:
ISchemaRegistry schemaRegistry = ISchemaRegistry(schemaRegistryAddress);
bytes32 schemaUID = schemaRegistry.register(
"bytes32 integraHash,uint256 tokenId,uint256 claimCapability",
address(tokenClaimResolver), // Resolver address
true // Revocable
);Security Considerations
Executor Authorization
When using executors:
- Only authorize trusted contracts as executors
- Executors have same attestation creation rights as owners
- Review executor logic carefully before authorization
- Can revoke executor authorization at any time
Attestation Expiration
Consider setting expiration times on attestations for time-sensitive claims:
expirationTime: block.timestamp + 7 days // Claim must happen within 7 daysRevocability
Always make claim attestations revocable so you can:
- Fix mistakes
- Respond to changed circumstances
- Revoke access if needed
View Functions
Check Authorization
function isAuthorizedAttester(bytes32 integraHash, address attester)
external view returns (bool)Check if an address can create claim attestations for a document.
Get Document Info
function getDocumentOwner(bytes32 integraHash)
external view returns (address)
function getDocumentExecutor(bytes32 integraHash)
external view returns (address)Get document owner and executor addresses.
Complete Integration Example
import { IEAS, AttestationRequest, AttestationRequestData } from "@eas/IEAS.sol";
import { IntegraDocumentRegistryV7_Immutable } from "@integra/IntegraDocumentRegistryV7_Immutable.sol";
import { IDocumentTokenizerV7 } from "@integra/IDocumentTokenizerV7.sol";
import { TokenClaimResolver } from "@integra/TokenClaimResolver.sol";
contract DocumentManager {
IEAS public immutable eas;
IntegraDocumentRegistryV7_Immutable public immutable documentRegistry;
IDocumentTokenizerV7 public tokenizer;
TokenClaimResolver public claimResolver;
bytes32 public claimSchemaUID;
uint256 constant CAPABILITY_CLAIM_TOKEN = 1 << 1;
constructor(
address _eas,
address _documentRegistry,
address _tokenizer,
address _claimResolver,
bytes32 _claimSchemaUID
) {
eas = IEAS(_eas);
documentRegistry = IntegraDocumentRegistryV7_Immutable(_documentRegistry);
tokenizer = IDocumentTokenizerV7(_tokenizer);
claimResolver = TokenClaimResolver(_claimResolver);
claimSchemaUID = _claimSchemaUID;
}
/**
* @notice Grant a user permission to claim a token
* @dev Only document owner can call this
*/
function grantClaimPermission(
bytes32 integraHash,
uint256 tokenId,
address recipient
) external returns (bytes32 attestationUID) {
// 1. Verify caller is document owner
require(
documentRegistry.getDocumentOwner(integraHash) == msg.sender,
"Not document owner"
);
// 2. Verify we can create attestations
require(
claimResolver.isAuthorizedAttester(integraHash, address(this)),
"Not authorized"
);
// 3. Reserve the token (optional, depending on workflow)
tokenizer.reserveToken(
integraHash,
tokenId,
recipient,
1,
bytes32(0)
);
// 4. Create claim attestation
bytes memory attestationData = abi.encode(
integraHash,
tokenId,
CAPABILITY_CLAIM_TOKEN
);
AttestationRequest memory request = AttestationRequest({
schema: claimSchemaUID,
data: AttestationRequestData({
recipient: recipient,
expirationTime: 0,
revocable: true,
refUID: bytes32(0),
data: attestationData,
value: 0
})
});
attestationUID = eas.attest(request);
emit ClaimPermissionGranted(integraHash, tokenId, recipient, attestationUID);
}
/**
* @notice Revoke a user's permission to claim
* @dev Only document owner can call this
*/
function revokeClaimPermission(
bytes32 integraHash,
bytes32 attestationUID
) external {
require(
documentRegistry.getDocumentOwner(integraHash) == msg.sender,
"Not document owner"
);
RevocationRequest memory request = RevocationRequest({
schema: claimSchemaUID,
data: RevocationRequestData({
uid: attestationUID,
value: 0
})
});
eas.revoke(request);
emit ClaimPermissionRevoked(integraHash, attestationUID);
}
event ClaimPermissionGranted(
bytes32 indexed integraHash,
uint256 indexed tokenId,
address indexed recipient,
bytes32 attestationUID
);
event ClaimPermissionRevoked(
bytes32 indexed integraHash,
bytes32 attestationUID
);
}References
- EAS Documentation - Ethereum Attestation Service
- Document Registry - Document management
- Tokenizers Overview - Token claim patterns
- SimpleContactResolverV7 - Another resolver example