GuidesLayer 2 Integration

Layer 2 Integration Guide

Integrating with Integra’s document layer contracts.

Overview

Layer 2 in Integra’s architecture encompasses the document-focused contracts that manage document identity, lifecycle, and service attachments. This guide covers integration with the Document Registry, Resolver Registry, and associated contracts.

Layer 2 Components

IntegraDocumentRegistry

The core document management contract:

interface IIntegraDocumentRegistry {
    function registerDocument(...) external returns (bytes32);
    function getDocument(bytes32 integraHash) external view returns (Document memory);
    function transferOwnership(bytes32 integraHash, address newOwner, string calldata reason) external;
}

ResolverRegistry

Manages resolver attachments:

interface IResolverRegistry {
    function setPrimaryResolver(bytes32 integraHash, bytes32 resolverId) external;
    function addAdditionalResolver(bytes32 integraHash, bytes32 resolverId) external;
    function getResolvers(bytes32 integraHash) external view returns (bytes32, bytes32[] memory);
}

Integration Steps

Step 1: Get Contract Addresses

import { getContractAddresses } from '@integra/sdk';
 
const addresses = await getContractAddresses('polygon');
const documentRegistry = addresses.documentRegistry;
const resolverRegistry = addresses.resolverRegistry;

Step 2: Initialize Contracts

import { ethers } from 'ethers';
import { IntegraDocumentRegistry__factory } from '@integra/contracts';
 
const registry = IntegraDocumentRegistry__factory.connect(
    documentRegistry,
    signer
);

Step 3: Register Documents

// Prepare document hash
const documentContent = await fs.readFile('contract.pdf');
const documentHash = ethers.utils.sha256(documentContent);
 
// Upload to IPFS and get CID
const ipfsCID = await uploadToIPFS(documentContent);
const referenceHash = cidToBytes32(ipfsCID);
 
// Register
const tx = await registry.registerDocument(
    documentHash,
    referenceHash,
    ownershipTokenizer,
    ethers.constants.AddressZero, // No executor
    ethers.constants.HashZero,    // No process hash
    ethers.constants.HashZero,    // No identity extension
    contactResolverId,            // Primary resolver
    []                            // No additional resolvers
);
 
const receipt = await tx.wait();
const event = receipt.events?.find(e => e.event === 'DocumentRegistered');
const integraHash = event?.args?.integraHash;

Step 4: Query Documents

// Get document details
const doc = await registry.getDocument(integraHash);
 
console.log({
    owner: doc.owner,
    tokenizer: doc.tokenizer,
    documentHash: doc.documentHash,
    referenceHash: doc.referenceHash,
});
 
// Check existence
const exists = await registry.documentExists(integraHash);

Step 5: Manage Resolvers

// Add additional resolver
await registry.addAdditionalResolver(
    integraHash,
    lifecycleResolverId
);
 
// Lock resolver configuration (permanent)
await registry.lockResolvers(integraHash);

Event Handling

Listen for Events

// Document registration events
registry.on('DocumentRegistered', (integraHash, documentHash, owner, tokenizer, processHash, event) => {
    console.log('New document registered:', {
        integraHash,
        owner,
        tokenizer,
        blockNumber: event.blockNumber,
    });
});
 
// Ownership transfer events
registry.on('OwnershipTransferred', (integraHash, oldOwner, newOwner, reason, event) => {
    console.log('Ownership transferred:', {
        integraHash,
        from: oldOwner,
        to: newOwner,
        reason,
    });
});

Historical Event Queries

// Get all documents registered by an address
const filter = registry.filters.DocumentRegistered(null, null, ownerAddress);
const events = await registry.queryFilter(filter, startBlock, 'latest');
 
const documents = events.map(e => ({
    integraHash: e.args.integraHash,
    documentHash: e.args.documentHash,
    tokenizer: e.args.tokenizer,
    timestamp: e.args.timestamp,
}));

Common Patterns

Pattern 1: Document with Tokenization

async function registerTokenizedDocument(
    documentContent: Buffer,
    recipient: string,
    tokenizer: string
) {
    // Hash and upload
    const documentHash = ethers.utils.sha256(documentContent);
    const ipfsCID = await uploadToIPFS(documentContent);
 
    // Register
    const tx = await registry.registerDocument(
        documentHash,
        cidToBytes32(ipfsCID),
        tokenizer,
        ethers.constants.AddressZero,
        ethers.constants.HashZero,
        ethers.constants.HashZero,
        ethers.constants.HashZero,
        []
    );
 
    const integraHash = await getIntegrahashFromTx(tx);
 
    // Reserve token for recipient
    const tokenizerContract = await ethers.getContractAt('OwnershipTokenizer', tokenizer);
    await tokenizerContract.reserveToken(integraHash, 1, recipient, ethers.constants.HashZero);
 
    return integraHash;
}

Pattern 2: Compliance-First Registration

async function registerCompliantDocument(
    documentContent: Buffer,
    complianceData: ComplianceData
) {
    const documentHash = ethers.utils.sha256(documentContent);
    const ipfsCID = await uploadToIPFS(documentContent);
 
    // Encode compliance metadata
    const complianceMetadata = ethers.utils.defaultAbiCoder.encode(
        ['string[]', 'uint256', 'address'],
        [complianceData.jurisdictions, complianceData.minimumInvestment, complianceData.complianceProvider]
    );
 
    // Register with compliance resolver as primary
    const tx = await registry.registerDocument(
        documentHash,
        cidToBytes32(ipfsCID),
        securityTokenTokenizer,
        ethers.constants.AddressZero,
        ethers.constants.HashZero,
        ethers.utils.keccak256(complianceMetadata), // Identity extension
        accreditationResolverId,  // Primary: compliance required
        [geographicResolverId]    // Additional: geographic restrictions
    );
 
    return getIntegrahashFromTx(tx);
}

Pattern 3: Batch Registration

async function registerDocumentBatch(documents: DocumentInput[]) {
    const results = [];
 
    for (const doc of documents) {
        const tx = await registry.registerDocument(
            doc.hash,
            doc.reference,
            doc.tokenizer,
            ethers.constants.AddressZero,
            doc.processHash, // Batch identifier
            ethers.constants.HashZero,
            doc.primaryResolver,
            doc.additionalResolvers
        );
 
        results.push({
            input: doc,
            integraHash: await getIntegrahashFromTx(tx),
        });
    }
 
    return results;
}

Error Handling

try {
    const tx = await registry.registerDocument(...);
    await tx.wait();
} catch (error) {
    if (error.reason === 'DocumentAlreadyExists') {
        // Handle duplicate registration
    } else if (error.reason === 'InvalidTokenizer') {
        // Handle invalid tokenizer
    } else if (error.reason === 'PrimaryResolverFailed') {
        // Handle resolver failure
    } else {
        throw error;
    }
}

Gas Optimization

Batch Operations

Use multicall for multiple read operations:

import { Multicall } from '@ethersproject/providers';
 
const calls = integraHashes.map(hash => ({
    target: registry.address,
    callData: registry.interface.encodeFunctionData('getDocument', [hash]),
}));
 
const results = await multicall.aggregate(calls);

Efficient Event Querying

// Use pagination for large result sets
const BLOCK_RANGE = 10000;
let fromBlock = startBlock;
const allEvents = [];
 
while (fromBlock < endBlock) {
    const toBlock = Math.min(fromBlock + BLOCK_RANGE, endBlock);
    const events = await registry.queryFilter(filter, fromBlock, toBlock);
    allEvents.push(...events);
    fromBlock = toBlock + 1;
}