Compliance Middleware for Real World Asset Tokenization
A B2B decentralized attestation infrastructure for Real World Assets (RWA) built on Ethereum. This system enables trusted service providers to create attestations that other businesses can consume and verify, forming a shared trust layer for the RWA ecosystem.
We are compliance middleware for RWAs — not a data oracle, not a general attestation service.
| What We Are | What We're NOT |
|---|---|
| Compliance layer for security tokens | Chainlink alternative (price feeds) |
| Trust anchor for RWA documentation | General-purpose attestation (like EAS) |
| ERC-3643 compatible identity registry | KYC provider (we certify, not verify) |
| Economic security via staking/slashing | Centralized compliance service |
Target Market: RWA tokenization platforms, security token issuers, institutional DeFi.
The RWA Attestation Layer is a trust and certification infrastructure — a notary layer for the blockchain ecosystem.
⚠️ Important Distinction: We do NOT perform verifications (KYC checks, property inspections, etc.). We attest that verifications done by authorized service providers are valid and anchor that proof on-chain.
┌───────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Service Provider │ │ RWA Attestation │ │ Consumer │
│ (Verifier) │ │ Layer (Notary) │ │ (DeFi, etc.) │
├───────────────────┤ ├──────────────────┤ ├──────────────────┤
│ │ │ │ │ │
│ Actually does │────▶│ Certifies the │────▶│ Trusts the │
│ the verification │ │ verification is │ │ on-chain │
│ (checks IDs, │ │ valid & anchors │ │ attestation │
│ inspects props) │ │ proof on-chain │ │ │
│ │ │ │ │ │
└───────────────────┘ └──────────────────┘ └──────────────────┘
VERIFIER ATTESTER/NOTARY CONSUMER
Think of us like a notary public — we don't write the document, we certify it's legitimate.
Businesses use this infrastructure to:
- Certify verifications performed by authorized service providers
- Anchor proof on-chain with document integrity via cryptographic hashes
- Enable trust composability across the RWA tokenization ecosystem
┌─────────────────────────────────────────────────────────────────────────┐
│ B2B ATTESTATION FLOW │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ATTESTERS CONSUMERS │
│ (Service Providers) (Business Clients) │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ KYC Providers │ │ DeFi Protocols │ │
│ │ (Jumio, Onfido) │────┐ ┌────│ (Lending, DEXs) │ │
│ └─────────────────┘ │ │ └─────────────────┘ │
│ │ │ │
│ ┌─────────────────┐ │ │ ┌─────────────────┐ │
│ │ Title Companies │────┤ ├────│ RWA Tokenizers │ │
│ │ (First American)│ │ │ │ (RealT, Centri) │ │
│ └─────────────────┘ │ │ └─────────────────┘ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ │
│ │ Appraisal Firms │──│ RWA ATTESTATION │──│ Inst. Investors │ │
│ │ │ │ LAYER │ │ (Funds, DAOs) │ │
│ └─────────────────┘ └──────────────────┘ └─────────────────┘ │
│ ▲ ▲ │
│ ┌─────────────────┐ │ │ ┌─────────────────┐ │
│ │ Insurance Cos │────┤ ├────│ Compliance │ │
│ │ │ │ │ │ Systems │ │
│ └─────────────────┘ │ │ └─────────────────┘ │
│ │ │ │
│ ┌─────────────────┐ │ │ ┌─────────────────┐ │
│ │ Legal/Audit │────┘ └────│ Real Estate │ │
│ │ Firms │ │ Marketplaces │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ ATTESTATION FLOW │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. SERVICE PROVIDER 2. ATTESTATION LAYER 3. CONSUMER │
│ does verification certifies it uses it │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌───────────────┐ │
│ │ KYC Provider │ │ │ │ DeFi Protocol │ │
│ │ checks user's │────────▶│ Attests that │────▶│ checks if │ │
│ │ ID documents │ │ KYC is valid │ │ user has │ │
│ └─────────────────┘ │ │ │ valid KYC │ │
│ │ ON-CHAIN: │ └───────────────┘ │
│ ┌─────────────────┐ │ - Hash anchor │ │
│ │ Title Company │ │ - Attester ID │ ┌───────────────┐ │
│ │ verifies │────────▶│ - Expiration │────▶│ Tokenization │ │
│ │ property deed │ │ - Schema ref │ │ Platform │ │
│ └─────────────────┘ │ │ └───────────────┘ │
│ │ OFF-CHAIN: │ │
│ ┌─────────────────┐ │ - Full doc │ ┌───────────────┐ │
│ │ Appraiser │ │ - IPFS stored │────▶│ Lender checks │ │
│ │ inspects & │────────▶│ - Verifiable │ │ collateral │ │
│ │ values property │ │ │ │ value │ │
│ └─────────────────┘ └─────────────────┘ └───────────────┘ │
│ │
│ THEY DO THE WORK WE CERTIFY IT THEY TRUST IT │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Service providers do the actual verification work, then use our platform to certify their findings:
| Provider Type | What They Verify | What We Attest | Business Value |
|---|---|---|---|
| KYC/AML Providers | Check IDs, run sanctions screening | "This KYC verification is valid" | Portable KYC across DeFi |
| Title Companies | Research deeds, verify ownership | "This title is clear" | On-chain title proof |
| Appraisal Firms | Inspect & value properties | "This valuation is certified" | Trusted collateral values |
| Insurance Companies | Underwrite policies | "This coverage is active" | Proof-of-insurance |
| Audit/Legal Firms | Conduct audits, legal reviews | "This entity is compliant" | Verifiable compliance |
| Business Type | Attestations They Need | Integration Benefit |
|---|---|---|
| DeFi Lending Protocols | KYC, property title, appraisal | Automate underwriting for RWA-backed loans |
| RWA Tokenization Platforms | Title, appraisal, insurance | Verify assets before tokenization |
| Institutional Investors | KYC, compliance certs | Meet regulatory requirements programmatically |
| Real Estate Marketplaces | Title, appraisal, insurance | Trust layer for property transactions |
| DAOs/Treasury Managers | All attestation types | Due diligence for RWA investments |
┌─────────────────────────────────────────────────────────────────────────┐
│ REAL-WORLD LOAN EXAMPLE │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ STEP 1: Verifications happen OFF our platform │
│ ════════════════════════════════════════════ │
│ • Jumio verifies borrower's identity (KYC) │
│ • First American verifies property title │
│ • Licensed appraiser values the property at $500K │
│ │
│ STEP 2: Attestations created ON our platform │
│ ════════════════════════════════════════════ │
│ • Jumio creates attestation: "User 0x123 passed KYC" (expires 6 mo) │
│ • First American attests: "Property deed is clear" │
│ • Appraiser attests: "Property valued at $500K" │
│ │
│ STEP 3: DeFi protocol queries our attestations │
│ ════════════════════════════════════════════ │
│ │
│ Protocol: "Can I lend to 0x123 against this property?" │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ attestation.isValid(kycId) │ ✓ KYC attested by Jumio │
│ │ attestation.isExpired(kycId) │ ✓ Not expired │
│ │ getAttestation(titleId).attester │ ✓ First American attested │
│ │ getAttestation(appraisalId) │ ✓ Value = $500K │
│ └─────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Loan approved: $350K (70% LTV of attested $500K value) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Key Point: The DeFi protocol trusts our attestations because:
- The attesters (Jumio, First American) are authorized on-chain
- The document hash matches (integrity verified)
- The attestation hasn't expired or been revoked
The RWA Attestation Layer provides a hybrid on-chain/off-chain architecture that:
- Anchors document integrity on-chain via cryptographic hashes
- Stores full attestation documents off-chain (IPFS/Pinata) for efficiency
- Supports schema-based validation ensuring document structure compliance
- Enables time-limited attestations with built-in expiration (e.g., 6-month KYC validity)
- Provides role-based access control for authorized attesters per schema
Attesters must stake ETH to participate. Fraudulent attestations result in slashed stakes.
┌─────────────────────────────────────────────────────────────────┐
│ ECONOMIC SECURITY MODEL │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ATTESTER ONBOARDING │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 1. Stake ETH (minimum required) │ │
│ │ 2. Get granted role for specific schema(s) │ │
│ │ 3. Can now create attestations │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ IF ATTESTATION IS FRAUDULENT │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 1. Anyone opens dispute (posts bond) │ │
│ │ 2. Dispute resolver investigates │ │
│ │ 3. If upheld: attester stake SLASHED (up to 50%) │ │
│ │ 4. Attestation revoked │ │
│ │ 5. Challenger bond returned + possible reward │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ WITHDRAWAL SAFEGUARDS │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ • 30-day initial lock period │ │
│ │ • 7-day withdrawal cooldown │ │
│ │ • Allows time to discover and dispute fraud │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
| Property | Mechanism | Result |
|---|---|---|
| Integrity | Document hash on-chain | Tamper-proof records |
| Accountability | Attester identity recorded | Reputation at stake |
| Economic Alignment | Staking + slashing | Cost of fraud > benefit |
| Dispute Resolution | Bond-based challenges | Community oversight |
| Compliance | ERC-3643 integration | Transfer restrictions enforced |
See docs/THREAT_MODEL.md for full security analysis.
┌─────────────────────────────────────────────────────────────────┐
│ Off-Chain Layer │
├─────────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ JSON Schemas │ │ Attestation │ │ Schema │ │
│ │ (Draft-07) │ │ Documents │ │ Validator │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └──────────────────┼──────────────────┘ │
│ │ │
│ ┌──────▼───────┐ │
│ │ IPFS/Pinata │ │
│ └──────┬───────┘ │
└────────────────────────────┼────────────────────────────────────┘
│ CID / Hash
┌────────────────────────────┼────────────────────────────────────┐
│ On-Chain Layer │
├────────────────────────────┼────────────────────────────────────┤
│ ┌──────▼───────┐ │
│ │ Attestation │ │
│ │ Contract │ │
│ └──────┬───────┘ │
│ │ │
│ ┌──────────────────┼──────────────────┐ │
│ │ │ │ │
│ ┌──────▼───────┐ ┌──────▼───────┐ ┌──────▼───────┐ │
│ │ SchemaRegistry│ │ AttesterRole │ │ Libraries │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
rwa-attestation-layer/
├── contracts/ # Solidity smart contracts
│ ├── Attestation.sol # Core attestation contract
│ ├── AttestationV2.sol # Enhanced: delegation, disputes, indexing
│ ├── AttesterRole.sol # Schema-scoped attester permissions
│ ├── AttesterStaking.sol # Economic security: stake to attest
│ ├── RWASchemaRegistry.sol # Schema registry with versioning
│ ├── RWACompliance.sol # ERC-3643 security token integration
│ ├── interfaces/ # Contract interfaces
│ │ ├── IAttestation.sol
│ │ ├── IAttestationV2.sol
│ │ ├── IAttesterRole.sol
│ │ ├── ISchemaRegistry.sol
│ │ └── IERC3643Compliance.sol
│ └── libraries/ # Utility libraries
│ ├── HashUtils.sol # Hashing helpers
│ └── SchemaValidator.sol # On-chain validation
├── schemas/ # JSON Schema definitions
│ ├── common-headers.json # Shared attestation headers
│ ├── kyc.schema.json # KYC/AML verification schema
│ ├── property.schema.json # Real estate attestation schema
│ ├── appraisal.schema.json # Property appraisal schema
│ └── insurance.schema.json # Insurance attestation schema
├── sdk/ # TypeScript SDK
│ └── src/
├── cli/ # Command-line tools
│ └── src/
├── subgraph/ # The Graph indexer
├── docs/ # Documentation
│ ├── INTEGRATION.md # Integration guide
│ ├── ATTESTER_GUIDE.md # For attesters
│ └── THREAT_MODEL.md # Security analysis
├── ipfs/ # Off-chain tooling
│ ├── uploadAttestation.js # Upload documents to IPFS
│ ├── verifyHash.js # Verify on-chain vs off-chain hash
│ └── schema-validator.js # JSON Schema validation
├── script/ # Deployment scripts
│ ├── DeployAll.s.sol # Full deployment
│ ├── DeployAttestation.s.sol
│ └── DeploySchemaRegistry.s.sol
├── test/ # Test suites
│ ├── Attestation.t.sol
│ ├── AttesterRole.t.sol
│ ├── SchemaRegistry.t.sol
│ └── integrations/
│ └── EndToEndAttestationV2.t.sol
└── lib/ # Dependencies (forge-std, OpenZeppelin)
The main attestation contract that stores attestation records on-chain.
Key Features:
- Create attestations with schema reference, subject, document hash, and optional URI
- Support for time-limited attestations with
expiresAttimestamp - Batch attestation and revocation (up to 50 per transaction)
- Reference linking for attestation chains/history
- Pausable operations for emergency scenarios
Data Structure:
struct AttestationRecord {
bytes32 id; // Unique attestation identifier
bytes32 schemaId; // Reference to registered schema
address attester; // Who created the attestation
address subject; // Who/what is being attested
bytes32 docHash; // Integrity hash of off-chain document
string docURI; // IPFS CID or URL for retrieval
bytes32 refId; // Optional reference to another attestation
uint32 version; // Document/schema version
uint64 time; // Creation timestamp
uint64 expiresAt; // Expiration timestamp (0 = never)
bool revoked; // Revocation status
uint64 revokedTime; // When revoked (0 if not revoked)
}Registry for attestation schemas with versioning support.
Key Features:
- Register schemas with hash and URI
- Version management with monotonic incrementing
- Activate/deactivate schemas
- Role-based admin control
Schema-scoped role registry for authorized attesters.
Key Features:
- Grant/revoke attester permissions per schema
- OpenZeppelin AccessControl integration
- Separate admin role for delegation
Lightweight hashing utilities:
keccakBytes()- Hash raw byteskeccakString()- Hash UTF-8 stringshashEnvelope()- Deterministic multi-field hashing
Minimal on-chain validation helpers for basic invariant checks.
Schemas define the structure of off-chain attestation documents using JSON Schema (Draft-07).
Shared fields across all attestations:
- attestationMeta: Schema ID, version, timestamps, references
- attesterInfo: Attester address, name, credentials
- subjectInfo: Subject identifier and type
- signature: Cryptographic signature details
| Schema | Description | Default Validity |
|---|---|---|
kyc.schema.json |
KYC/AML verification | 6 months |
property.schema.json |
Real estate property attestation | N/A |
appraisal.schema.json |
Property appraisal | 1 year typical |
insurance.schema.json |
Insurance coverage | Policy period |
{
"kyc": {
"level": "enhanced",
"status": "verified",
"verifiedAt": "2026-01-15T10:30:00Z",
"validUntil": "2026-07-15T10:30:00Z",
"checks": {
"identityVerified": true,
"addressVerified": true,
"amlScreening": "passed"
}
}
}Validates, hashes, and uploads attestation documents to IPFS via Pinata.
cd ipfs
npm install
node uploadAttestation.js --file my-kyc.json --schema kycCompares off-chain document hash with on-chain stored hash.
node verifyHash.js --attestation-id 0x... --contract 0x... --rpc https://...Validates JSON documents against their schemas before upload.
import { validateDocument } from './schema-validator.js';
const result = await validateDocument(kycDoc, 'kyc.schema.json');
if (!result.valid) {
console.error(result.errors);
}- Foundry
- Node.js 18+ (for IPFS tooling)
- Pinata account (for IPFS uploads)
# Clone the repository
git clone https://github.com/your-org/rwa-attestation-layer.git
cd rwa-attestation-layer
# Install Foundry dependencies
forge install
# Install Node.js dependencies for IPFS tooling
cd ipfs && npm install && cd ..forge buildforge testforge test -vvvforge snapshotforge fmtStart a local Anvil node:
anvilDeploy contracts locally:
forge script script/LocalMockSetup.s.sol --rpc-url http://localhost:8545 --broadcastDeploy to a network:
forge script script/DeployAttestation.s.sol \
--rpc-url <RPC_URL> \
--private-key <PRIVATE_KEY> \
--broadcast \
--verifybytes32 schemaId = keccak256("kyc.v1");
bytes32 schemaHash = keccak256(schemaJsonBytes);
string memory schemaUri = "ipfs://Qm...";
schemaRegistry.registerSchema(schemaId, schemaHash, schemaUri, 1);attesterRole.grantSchemaAttester(schemaId, attesterAddress);const kycDoc = {
meta: { schemaId: "0x...", schemaVersion: 1, ... },
attester: { address: "0x...", name: "KYC Provider" },
subject: { identifier: "0x...", type: "ethereum-address" },
kyc: { level: "enhanced", status: "verified", ... }
};const { cid, docHash } = await uploadAttestation(kycDoc, 'kyc');
// cid: "bafybeig..."
// docHash: "0x..."bytes32 attestationId = attestation.attest(
schemaId, // Schema reference
subjectAddress, // Who is attested
docHash, // keccak256 of canonical JSON
"ipfs://bafybeig...", // Document URI
bytes32(0), // No reference
1, // Version
uint64(block.timestamp + 180 days) // Expires in 6 months
);const isValid = await verifyAttestation({
attestationId: "0x...",
contractAddress: "0x...",
provider: ethersProvider
});- Role-based Access: Only authorized attesters can create attestations for specific schemas
- Document Integrity: Off-chain documents are integrity-verified via keccak256 hash anchoring
- Expiration Support: Time-limited attestations prevent stale data usage
- Pausable: Emergency pause functionality for all attestation operations
- Revocation: Original attesters can revoke their attestations
This infrastructure enables several B2B revenue models:
| Model | Description |
|---|---|
| Per-Attestation Fees | Attesters charge for each verification issued |
| Subscription Access | Consumers pay monthly for attestation queries |
| Revenue Sharing | Protocol takes fee on attestation creation/verification |
| Enterprise Licensing | White-label deployments for large institutions |
| Data Marketplace | Aggregated anonymized attestation analytics |
MIT License - see LICENSE for details.
Contributions are welcome! Please read our contributing guidelines before submitting PRs.
This section explains every file in the project and how they work together.
┌────────────────────────────────────────────────────────────────────────────────────────┐
│ COMPLETE SYSTEM ARCHITECTURE │
├────────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ OFF-CHAIN LAYER (ipfs/) │ │
│ ├─────────────────────────────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ │
│ │ │ schema-validator │ │ uploadAttestation│ │ verifyHash │ │ │
│ │ │ .js │ │ .js │ │ .js │ │ │
│ │ ├──────────────────┤ ├──────────────────┤ ├──────────────────┤ │ │
│ │ │ Validates docs │───▶│ Uploads to IPFS │ │ Verifies on-chain│ │ │
│ │ │ against schemas │ │ Returns CID+hash │ │ vs off-chain hash│ │ │
│ │ └──────────────────┘ └────────┬─────────┘ └────────▲─────────┘ │ │
│ │ │ │ │ │ │
│ │ │ uses │ uploads to │ fetches from │ │
│ │ ▼ ▼ │ │ │
│ │ ┌──────────────────┐ ┌──────────────────┐ │ │ │
│ │ │ schemas/*.json │ │ IPFS (Pinata) │─────────────┘ │ │
│ │ │ (JSON Schema) │ │ Cloud Storage │ │ │
│ │ └──────────────────┘ └────────┬─────────┘ │ │
│ │ │ │ │
│ └───────────────────────────────────┼─────────────────────────────────────────────┘ │
│ │ CID + docHash │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ ON-CHAIN LAYER (contracts/) │ │
│ ├─────────────────────────────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ ┌────────────────────────┐ │ │
│ │ │ Attestation.sol │ ◄─── MAIN ENTRY POINT │ │
│ │ │ (Core Contract) │ │ │
│ │ ├────────────────────────┤ │ │
│ │ │ • attest() │ │ │
│ │ │ • batchAttest() │ │ │
│ │ │ • revoke() │ │ │
│ │ │ • isValid() │ │ │
│ │ │ • getAttestation() │ │ │
│ │ └───────────┬────────────┘ │ │
│ │ │ │ │
│ │ ┌────────────────────┼────────────────────┐ │ │
│ │ │ │ │ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌────────────────────┐ ┌────────────────────┐ ┌────────────────────┐ │ │
│ │ │ RWASchemaRegistry │ │ AttesterRole │ │ Libraries │ │ │
│ │ │ .sol │ │ .sol │ │ │ │ │
│ │ ├────────────────────┤ ├────────────────────┤ ├────────────────────┤ │ │
│ │ │ • registerSchema() │ │ • grantAttester() │ │ • HashUtils.sol │ │ │
│ │ │ • isActive() │ │ • revokeAttester() │ │ • SchemaValidator │ │ │
│ │ │ • schemaHash() │ │ • isAuthorized() │ │ .sol │ │ │
│ │ └────────────────────┘ └────────────────────┘ └────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────────────────────────┘
Purpose: The main contract that stores and manages attestation records.
What it does:
Service Provider Attestation.sol Consumer
│ │ │
│ 1. attest(schemaId, subject, │ │
│ docHash, docURI, ...) │ │
│───────────────────────────────────▶│ │
│ │ │
│ │ Validates: │
│ │ ├─ Schema exists & active? │
│ │ ├─ Caller authorized for schema? │
│ │ ├─ Subject not zero address? │
│ │ └─ DocHash not empty? │
│ │ │
│ 2. Returns attestationId │ │
│◀───────────────────────────────────│ │
│ │ │
│ │ 3. isValid(attestationId)? │
│ │◀────────────────────────────────────│
│ │ │
│ │ 4. Returns true/false │
│ │─────────────────────────────────────▶│
Key Functions:
| Function | Who Calls It | What It Does |
|---|---|---|
attest() |
Authorized attesters | Creates new attestation record |
batchAttest() |
Authorized attesters | Creates up to 50 attestations in one tx |
revoke() |
Original attester only | Marks attestation as revoked |
isValid() |
Anyone (consumers) | Returns true if: exists + not revoked + not expired |
isExpired() |
Anyone | Checks if past expiration timestamp |
getAttestation() |
Anyone | Returns full attestation record |
Dependencies:
ISchemaRegistry→ Checks if schema exists and is activeIAttesterRole→ Checks if caller is authorized for the schemaPausable→ Emergency stop functionalityOwnable→ Admin can pause/unpause
Purpose: Registry that tracks all valid attestation schemas.
Why it exists: Before anyone can create attestations, the schema must be registered. This ensures:
- Only approved document types can be attested
- Schema versions are tracked
- Schemas can be deprecated
Data Stored:
struct SchemaRecord {
bytes32 schemaHash; // Hash of the JSON schema (integrity check)
string schemaURI; // IPFS link to the actual schema JSON
uint64 createdAt; // When registered
uint32 version; // Version number (1, 2, 3...)
bool active; // Can be deactivated to stop new attestations
}How it connects:
Admin RWASchemaRegistry Attestation.sol
│ │ │
│ registerSchema( │ │
│ schemaId, │ │
│ schemaHash, │ │
│ schemaURI, │ │
│ version) │ │
│─────────────────────────────▶│ │
│ │ │
│ │◀──────────────────────────────────│
│ │ exists(schemaId)? │
│ │ isActive(schemaId)? │
│ │──────────────────────────────────▶│
│ │ true/false │
Purpose: Controls WHO can create attestations for WHICH schemas.
Key Concept: Different schemas may have different authorized attesters:
Schema: "kyc.v1" → Authorized: [Jumio, Onfido, Sumsub]
Schema: "property.v1" → Authorized: [FirstAmerican, Fidelity]
Schema: "appraisal.v1"→ Authorized: [LicensedAppraisers...]
How it works:
// Admin grants permission
attesterRole.grantSchemaAttester(
keccak256("kyc.v1"), // schemaId
0x1234... // Jumio's address
);
// Later, when Jumio tries to attest:
// Attestation.sol calls:
attesterRole.isAuthorizedAttester(msg.sender, schemaId);
// Returns true only if msg.sender was granted role for that schemaRole Hierarchy:
DEFAULT_ADMIN_ROLE (OpenZeppelin built-in)
│
▼
ROLE_ADMIN ──────▶ Can grant/revoke schema attesters
│
▼
ATTESTER_ROLE_{schemaId} ──────▶ Can create attestations for that schema
Purpose: Define the function signatures that contracts must implement.
| Interface | Defines | Used By |
|---|---|---|
IAttestation.sol |
Structs (AttestationRecord, AttestationRequest) + Events + Functions |
External integrations |
ISchemaRegistry.sol |
SchemaRecord struct + Events + Functions |
Attestation.sol |
IAttesterRole.sol |
isAuthorizedAttester() function |
Attestation.sol |
Why interfaces matter:
// Attestation.sol doesn't care HOW these are implemented
// It just calls through the interface:
ISchemaRegistry public immutable SCHEMA_REGISTRY;
IAttesterRole public immutable ATTESTER_ROLE;
// This allows swapping implementations laterHashUtils.sol:
// Used for hashing documents, URIs, etc.
HashUtils.keccakBytes(documentBytes); // Hash raw bytes
HashUtils.keccakString("ipfs://..."); // Hash strings
HashUtils.hashEnvelope(...); // Hash multiple fields togetherSchemaValidator.sol:
// Basic on-chain validation (NOT full JSON schema validation - that's off-chain)
SchemaValidator.validateAttestationBasics(schemaId, docHash, version);
// Checks: schemaId != 0, docHash != 0Purpose: Define the STRUCTURE of off-chain attestation documents.
┌─────────────────────────────────────────────────────────────────────────┐
│ SCHEMA FLOW │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. DEFINE SCHEMA (off-chain) │
│ schemas/kyc.schema.json │
│ ┌─────────────────────────────────────┐ │
│ │ { │ │
│ │ "kyc": { │ │
│ │ "level": "enhanced", │ │
│ │ "status": "verified" │ │
│ │ } │ │
│ │ } │ │
│ └─────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 2. REGISTER SCHEMA (on-chain) │
│ schemaRegistry.registerSchema( │
│ schemaId: keccak256("kyc.v1"), │
│ schemaHash: keccak256(schemaJsonBytes), ◄── Integrity │
│ schemaURI: "ipfs://Qm...", ◄── Where to find it │
│ version: 1 │
│ ) │
│ │ │
│ ▼ │
│ 3. VALIDATE DOCUMENT (off-chain) │
│ schema-validator.js validates against kyc.schema.json │
│ │ │
│ ▼ │
│ 4. CREATE ATTESTATION (on-chain) │
│ attestation.attest(schemaId, ..., docHash, ...) │
│ └── References the registered schema │
│ │
└─────────────────────────────────────────────────────────────────────────┘
| File | Purpose | Key Fields |
|---|---|---|
common-headers.json |
Shared structure ALL attestations must have | meta, attester, subject, signature |
kyc.schema.json |
KYC verification structure | kyc.level, kyc.status, identity, aml |
property.schema.json |
Property title verification | property, title, verification |
appraisal.schema.json |
Property valuation | appraisal, valuation, methodology |
insurance.schema.json |
Insurance coverage | policy, coverage, insuredAsset |
Common Headers (every attestation has these):
{
"meta": {
"schemaId": "0x...", // Links to on-chain schema
"schemaVersion": 1,
"createdAt": "2026-01-17T..."
},
"attester": {
"address": "0x...", // Who created this
"name": "Jumio Inc"
},
"subject": {
"address": "0x...", // Who/what is being attested
"type": "individual"
}
}Purpose: Validates documents against JSON schemas BEFORE uploading.
// Usage:
const result = await validateDocument('kyc', kycDocument);
// result: { valid: true/false, errors: [...] }Flow:
Document JSON ──▶ Load Schema ──▶ AJV Validator ──▶ Valid? ──▶ Proceed to upload
│ │
│ └── If invalid, stop & show errors
│
└── Uses common-headers.json for shared fields
Purpose: Uploads validated documents to IPFS and returns the hash needed for on-chain attestation.
What it does:
Document JSON uploadAttestation.js IPFS (Pinata)
│ │ │
│ uploadAttestation(doc) │ │
│─────────────────────────────▶│ │
│ │ │
│ │ 1. Canonicalize JSON │
│ │ (sorted keys, no whitespace) │
│ │ │
│ │ 2. Compute keccak256 hash │
│ │ docHash = keccak256(canonical) │
│ │ │
│ │ 3. Upload to Pinata │
│ │─────────────────────────────────────▶│
│ │ │
│ │◀─────────────────────────────────────│
│ │ CID returned │
│ │ │
│◀─────────────────────────────│ │
│ { cid, docHash, uri } │ │
│ │ │
│ USE docHash in attest() call on-chain │
Critical Point: The docHash returned here is what you pass to Attestation.attest(). This links the on-chain record to the off-chain document.
Purpose: Consumers use this to verify an attestation is legitimate.
Verification Flow:
┌─────────────────────────────────────────────────────────────────────────┐
│ VERIFICATION PROCESS │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Consumer wants to verify attestation 0xABC... │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 1. Fetch on-chain attestation │ │
│ │ contract.getAttestation(0xABC...) │ │
│ │ Returns: { docHash: 0x123..., docURI: "ipfs://..." } │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 2. Fetch off-chain document from IPFS │ │
│ │ fetch(docURI) → JSON document │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 3. Compute hash of fetched document │ │
│ │ computedHash = keccak256(canonicalize(document)) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 4. Compare hashes │ │
│ │ computedHash === onChainDocHash ? │ │
│ │ ├─ YES → Document hasn't been tampered │ │
│ │ └─ NO → Document was modified! Don't trust it │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 5. Additional checks │ │
│ │ • Is attestation revoked? (r.revoked == false) │ │
│ │ • Is attestation expired? (block.timestamp < expiry) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ATTESTATION VALID ✓ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Let's trace a KYC attestation from start to finish:
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ END-TO-END: KYC ATTESTATION LIFECYCLE │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ PHASE 1: SETUP (One-time by Admin) │
│ ═══════════════════════════════════ │
│ │
│ 1. Deploy contracts │
│ RWASchemaRegistry deployed at 0xAAA... │
│ AttesterRole deployed at 0xBBB... │
│ Attestation deployed at 0xCCC... (linked to above) │
│ │
│ 2. Register KYC schema │
│ schemaRegistry.registerSchema( │
│ keccak256("kyc.v1"), // schemaId │
│ keccak256(kycSchemaJson), // schemaHash │
│ "ipfs://QmKYCSchema...", // schemaURI │
│ 1 // version │
│ ) │
│ │
│ 3. Authorize Jumio as attester for KYC │
│ attesterRole.grantSchemaAttester( │
│ keccak256("kyc.v1"), // schemaId │
│ 0xJUMIO... // Jumio's address │
│ ) │
│ │
│ ───────────────────────────────────────────────────────────────────────────────────── │
│ │
│ PHASE 2: ATTESTATION CREATION (By Jumio) │
│ ═════════════════════════════════════════ │
│ │
│ 4. Jumio verifies user's identity (OFF-CHAIN, their own systems) │
│ └── Checks passport, runs AML screening, etc. │
│ │
│ 5. Jumio creates attestation document │
│ { │
│ "meta": { "schemaId": "0x...", "createdAt": "..." }, │
│ "attester": { "address": "0xJUMIO", "name": "Jumio" }, │
│ "subject": { "address": "0xUSER", "type": "individual" }, │
│ "kyc": { "level": "enhanced", "status": "verified", ... } │
│ } │
│ │
│ 6. Validate document against schema │
│ validateDocument('kyc', document) → { valid: true } │
│ │
│ 7. Upload to IPFS │
│ uploadAttestation(document, 'kyc') │
│ → { cid: "bafybei...", docHash: "0xDOCHASH...", uri: "ipfs://bafybei..." } │
│ │
│ 8. Create on-chain attestation │
│ attestation.attest( │
│ keccak256("kyc.v1"), // schemaId │
│ 0xUSER, // subject │
│ 0xDOCHASH, // docHash (from step 7) │
│ "ipfs://bafybei...", // docURI │
│ bytes32(0), // no reference │
│ 1, // version │
│ block.timestamp + 180 days // expires in 6 months │
│ ) │
│ → Returns attestationId: 0xATTEST... │
│ │
│ ───────────────────────────────────────────────────────────────────────────────────── │
│ │
│ PHASE 3: VERIFICATION (By DeFi Protocol) │
│ ═════════════════════════════════════════ │
│ │
│ 9. DeFi protocol checks if user has valid KYC │
│ attestation.isValid(0xATTEST...) → true │
│ │
│ 10. Protocol fetches full details │
│ attestation.getAttestation(0xATTEST...) │
│ → { │
│ attester: 0xJUMIO, // Trusted KYC provider │
│ subject: 0xUSER, // The user │
│ docHash: 0xDOCHASH, // For integrity check │
│ docURI: "ipfs://...", // To fetch full doc │
│ expiresAt: 1752710400, // July 2026 │
│ revoked: false │
│ } │
│ │
│ 11. Protocol verifies document integrity (optional deep check) │
│ verifyAttestation({ │
│ attestationId: "0xATTEST...", │
│ contractAddress: "0xCCC...", │
│ rpcUrl: "https://..." │
│ }) │
│ → { valid: true, hashMatch: true, notRevoked: true, notExpired: true } │
│ │
│ 12. Protocol trusts the attestation and allows user to proceed │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘
| File/Folder | Purpose |
|---|---|
foundry.toml |
Foundry configuration (compiler version, optimizer settings, etc.) |
remappings.txt |
Solidity import path mappings (@openzeppelin/ → lib/openzeppelin-contracts/) |
script/ |
Deployment scripts for Forge |
test/ |
Foundry test files (.t.sol) |
lib/ |
Git submodules: forge-std, openzeppelin-contracts |
ipfs/pinata.config.json |
IPFS gateway and API configuration |
ipfs/package.json |
Node.js dependencies for off-chain tools |
┌─────────────────┐
│ Attestation │
│ .sol │
└────────┬────────┘
│
┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ ISchemaRegistry │ │ IAttesterRole │ │ Pausable │
│ (interface) │ │ (interface) │ │ Ownable │
└────────┬────────┘ └────────┬────────┘ │ (OpenZeppelin) │
│ │ └─────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│RWASchemaRegistry│ │ AttesterRole │
│ .sol │ │ .sol │
└────────┬────────┘ └────────┬────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ AccessControl │ │ AccessControl │
│ Pausable │ │ (OpenZeppelin) │
│ (OpenZeppelin) │ └─────────────────┘
└─────────────────┘