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;
}Related Documentation
- Document Registry - Registry deep dive
- Architecture Overview - System architecture
- Resolver Development - Creating resolvers
- Integration Guide - General integration