Skip to main content

Architecture Overview

System architecture for the Integra Protocol v1.7 smart contracts.

System Overview

The Integra protocol is an 11-layer smart contract system for on-chain document identity. Each layer has a single responsibility and communicates with adjacent layers through immutable contract references set at deployment. The entire system is built on Solidity 0.8.28 with OpenZeppelin v5.x and compiles with no upgradeability -- every contract is deployed once and is immutable.

The protocol is organized into 11 layers: Core, Access Control, Records, Execution, Messaging, Extensions, Lens, Policy, Interfaces, Tokenizers, and Resolvers. All tokenizers implement IContractV2 for uniform state and action discovery, and ITokenParty for cross-standard token holder detection.

graph TB
    subgraph L11["Layer 11: Resolvers"]
        BR["BaseResolver"]
        ADR["ADRResolverV3"]
        AAA["AAAResolverV1"]
    end

    subgraph L10["Layer 10: Tokenizers"]
        ERC721["ERC-721 Tokenizers<br/>(Ownership, Escrow, Soulbound, ...)"]
        ERC1155["ERC-1155 Tokenizers<br/>(Agreement, MultiParty, Royalty, ...)"]
        ERC20["ERC-20 Tokenizers<br/>(Fractional, Governance, Shares, ...)"]
    end

    subgraph L9["Layer 9: Interfaces"]
        ICV2["IContractV2"]
        ITP["ITokenParty"]
        IT2["ITokenizerV2"]
        IR2["IResolverV2"]
    end

    subgraph L8["Layer 8: Policy"]
        ATP["AttestationTransferPolicy"]
        TP["TransitionPolicy"]
    end

    subgraph L7["Layer 7: Lens"]
        LENS["IntegraLens"]
    end

    subgraph L6["Layer 6: Extensions"]
        RE["RecordExtension (abstract base)"]
        BB["Building Blocks<br/>(CustodyChain, DataCommitment,<br/>FundsCustody, PaymentLedger)"]
        TB["Token Bindings<br/>(TokenBinding721/1155/20)"]
        TE["Diamond Resolvers<br/>(TokenizedExtension721/1155/20)"]
        CE["Concrete Extensions<br/>(Ownership, Escrow, Approval)"]
    end

    subgraph L5["Layer 5: Messaging"]
        SIG["IntegraSignalV1"]
        MSG["IntegraMessageV1"]
    end

    subgraph L4["Layer 4: Execution"]
        EXEC["IntegraExecutorV1"]
        FWD["IntegraForwarder"]
    end

    subgraph L3["Layer 3: Records"]
        REC["IntegraRecordV1"]
    end

    subgraph L2["Layer 2: Access Control"]
        AAC["AttestationAccessControlV1<br/>(abstract)"]
        EAS["EASAttestationProviderV1"]
    end

    subgraph L1["Layer 1: Core"]
        EXIST["IntegraExistenceV1"]
        REG["IntegraRegistryV1"]
        LED["IntegraLedgerV1"]
        CAP["CapabilityNamespaceV1"]
    end

    %% Vertical dependencies
    REC -->|"immutable ref"| EXIST
    REC -->|"immutable ref"| REG
    AAC -->|"immutable ref"| REG
    AAC -->|"immutable ref"| CAP
    EAS -->|"immutable ref"| CAP
    EAS -->|"immutable ref"| REC
    ERC721 -->|"inherits"| AAC
    ERC1155 -->|"inherits"| AAC
    ERC20 -->|"inherits"| AAC
    ERC721 -->|"immutable ref"| REC
    ERC1155 -->|"immutable ref"| REC
    ERC20 -->|"immutable ref"| REC
    ADR -->|"immutable ref"| REC
    AAA -->|"immutable ref"| REC
    SIG -->|"immutable ref"| REC
    LENS -->|"immutable ref"| REC
    LENS -->|"immutable ref"| REG
    RE -->|"immutable ref"| REC
    RE -->|"inherits"| AAC
    ATP -->|"immutable ref"| REC
    FWD -.->|"trusted forwarder"| REC
    FWD -.->|"trusted forwarder"| ERC721
    FWD -.->|"trusted forwarder"| ADR
    ERC721 -.->|"implements"| ICV2
    ERC1155 -.->|"implements"| ICV2
    ERC20 -.->|"implements"| ICV2
    ERC721 -.->|"implements"| ITP
    ERC1155 -.->|"implements"| ITP
    ERC20 -.->|"implements"| ITP
    ADR -.->|"implements"| ICV2
    AAA -.->|"implements"| ICV2
    LENS -.->|"calls"| ICV2
    ATP -.->|"implements"| ICV2

The layers, bottom to top:

#LayerPurpose
1CoreImmutable proof-of-existence, capability definitions, registries, and protocol directory
2Access ControlAttestation-gated capability verification through provider abstraction
3RecordsDocument ownership, metadata, tokenizer/resolver binding, and policy resolver attachment
4ExecutionGas abstraction and ERC-2771 meta-transaction forwarding
5MessagingEncrypted payment requests and ZK-gated workflow messages between token holders
6ExtensionsComposable building blocks for domain-specific record logic
7LensStateless view composition across all contracts attached to a record
8PolicyTransfer authorization policies for tokenized records
9InterfacesUniversal introspection: IContractV2, ITokenParty, ITokenizerV2, IResolverV2
10Tokenizers23 token contracts representing document rights across ERC-721, ERC-1155, and ERC-20
11ResolversPluggable on-chain logic for dispute resolution, compliance, and lifecycle management

Layer Descriptions

Layer 1: Core

Four contracts that form the foundation of the protocol. Every other layer depends on at least one core contract.

IntegraExistenceV1

Source: src/registry/IntegraExistenceV1.sol

A minimal, write-once registry for content hash proof-of-existence. Has no admin functions, no access control, and no upgradeability. Once a content hash is registered, it cannot be re-registered or modified. First registration wins.

Key properties:

  • Permissionless -- any address can register a content hash.
  • Write-once -- subsequent registrations of the same hash revert with AlreadyExists.
  • Batch support -- registerBatch() skips already-registered hashes (no revert on duplicates within a batch).
  • No dependencies -- no imports beyond Solidity itself.
function register(bytes32 contentHash) external returns (uint64 timestamp);
function exists(bytes32 contentHash) external view returns (bool);

IntegraLedgerV1

Source: src/core/registries/IntegraLedgerV1.sol

An on-chain directory of all Integra protocol contracts on a given chain. Governance-controlled (GOVERNOR_ROLE). Supports both bytes32 key lookups (gas-efficient) and human-readable string name lookups. The key array is append-only, ensuring stable indices.

The ledger stores code-hash integrity tracking (codeHashes), EAS attestation UIDs (attestationUids), and IPFS metadata URIs (metadataUris) for every registered contract. getContractInfo() returns 8 fields and getAllContractsWithMetadata() returns 9 parallel arrays.

function getContract(string calldata name) external view returns (address);
function getAddress(bytes32 key) external view returns (address);
function getContractInfo(string calldata name) external view returns (...);

IntegraRegistryV1

Source: src/core/registries/IntegraRegistryV1.sol

A permissionless component registry with publisher sovereignty. Any address can register a provider, verifier, resolver, tokenizer, or policy contract by paying a fee. Publishers retain full control over their records (activate/deactivate, update metadata). Governance is limited to economic parameters (fee amounts, fee recipient).

The RegistryType enum includes five component types, with POLICY (value 4) supporting transfer policy resolvers.

Components are categorized by RegistryType:

TypeDescription
PROVIDERAttestation providers (EAS, VC, ZK, DID)
VERIFIERZK proof verifiers (Groth16, PLONK)
RESOLVERRecord resolvers (lifecycle, compliance)
TOKENIZERToken implementations (ERC-721, ERC-1155, ERC-20)
POLICYTransfer policy resolvers (attestation-gated transfers)

Trust differentiation is handled via EAS attestations, not registry approval. The registry captures the extcodehash at registration time and validates it on every getComponent() call -- if the code at the address changes, the component is silently treated as invalid.

function registerComponent(address, RegistryType, bytes32, string calldata, address)
    external payable returns (bytes32 componentId);
function getComponent(bytes32 componentId) external view returns (address);
// Returns address(0) if inactive, not found, or code hash changed

CapabilityNamespaceV1

Source: src/core/capabilities/CapabilityNamespaceV1.sol

A permanent, non-upgradeable definition of capability bit positions. Deployed once per chain. All contracts that check capabilities reference these same bit positions, ensuring consistent meaning across the entire protocol and across time.

The 256-bit namespace is organized into tiers:

BitsTierExamples
0--7CoreCORE_VIEW, CORE_CLAIM, CORE_TRANSFER, CORE_UPDATE, CORE_ADMIN
8--15Record OperationsDOC_SIGN, DOC_WITNESS, DOC_NOTARIZE, DOC_VERIFY, DOC_AMEND
16--23FinancialFIN_REQUEST_PAYMENT, FIN_APPROVE_PAYMENT, FIN_EXECUTE_PAYMENT
24--31GovernanceGOV_PROPOSE, GOV_VOTE, GOV_EXECUTE, GOV_VETO
32--255ReservedReserved for future protocol extensions

Pre-composed role templates combine capabilities into common profiles:

ROLE_VIEWER      = CORE_VIEW
ROLE_PARTICIPANT = CORE_VIEW | CORE_CLAIM | CORE_TRANSFER | FIN_REQUEST_PAYMENT
ROLE_MANAGER     = ROLE_PARTICIPANT | CORE_UPDATE | FIN_APPROVE_PAYMENT | DOC_SIGN | DOC_WITNESS
ROLE_ADMIN       = (1 << 128) - 1  // all bits 0-127

The CORE_ADMIN bit (bit 7) acts as a superuser flag -- when set, hasCapability() returns true for any required capability.


Layer 2: Access Control

AttestationAccessControlV1

Source: src/core/access/AttestationAccessControlV1.sol

An abstract base contract that gates operations behind attestation-verified capabilities. All tokenizers and extensions inherit from this contract. It holds immutable references to CapabilityNamespaceV1 and IntegraRegistryV1, and supports multiple attestation systems through the IAttestationProvider interface.

The core verification flow:

  1. Determine the provider for the record (recordProvider[contentHash] or defaultProviderId).
  2. Look up the provider address from IntegraRegistryV1.
  3. Call IAttestationProvider.verifyCapabilities() on the provider.
  4. Check that granted capabilities satisfy the required capability via CapabilityNamespaceV1.hasCapability().

This contract also implements progressive ossification and a custom transient storage reentrancy guard (see Security).

modifier requiresCapability(
    bytes32 contentHash,
    uint256 requiredCapability,
    bytes calldata attestationProof
);

EASAttestationProviderV1

Source: src/core/providers/EASAttestationProviderV1.sol

The reference IAttestationProvider implementation backed by the Ethereum Attestation Service. Performs a 13-step verification pipeline including:

  • Cross-chain replay prevention (validates sourceChainId)
  • EAS contract verification (validates sourceEASContract)
  • Record contract binding (validates recordContract matches this deployment)
  • Schema version validation (INTEGRA_CAPABILITY_V1.0.0)
  • Attestation expiration and revocation checks
  • Issuer authorization (record owner or owner-delegated issuer)
  • Capability bitmask extraction

Holds immutable references to the EAS contract, the access capability schema UID, CapabilityNamespaceV1, and IntegraRecordV1.


Layer 3: Records

IntegraRecordV1

Source: src/registry/IntegraRecordV1.sol

The central contract of the protocol. It manages document ownership, metadata, tokenizer associations, resolver bindings, policy resolver attachment, and executor authorization. Each record is identified by an integraHash (globally unique bytes32).

Registration is a multi-step atomic operation:

  1. Ensure existence -- registers the contentHash in IntegraExistenceV1 (if not already registered).
  2. Collect fee -- collects the registration fee in ETH or ERC-20.
  3. Validate inputs -- checks integraHash uniqueness, tokenizer approval, resolver existence, ZK proof for parent references.
  4. Create record -- stores ownership, content hash, identity extension, reference hash, tokenizer, resolver, and policy resolver.
  5. Authorize executor -- sets the per-record executor if provided.
  6. Emit events -- RecordCreated, RegistrationConfirmed, Referenced, TokenizerAssociated, ExecutorAuthorized.
  7. Invoke resolver hooks -- calls onRegistered() on all attached behavioral resolvers.

Key features:

  • Per-record executor -- the owner can delegate operations to a specific executor address per record, enabling gasless workflows.
  • Resolver array -- each record can attach up to 10 resolvers, which are notified of lifecycle events.
  • Policy resolver -- per-record transfer policy resolver, changeable by the owner, validated against IntegraRegistryV1.
  • Resolver locking -- once locked, resolver configuration becomes immutable (with time-limited emergency override).
  • Emergency controls -- a separate emergency address has time-limited override powers (180 days from deployment).
  • ZK-verified parent references -- when referenceHash is non-zero, a Groth16 proof is required.
function register(RegistrationParams calldata params, ProofData calldata proof)
    external payable returns (bytes32);
function transferOwnership(bytes32 integraHash, address newOwner, string calldata reason)
    external;
function setPolicyResolver(bytes32 integraHash, address policyAddress) external;
function authorizeExecutor(bytes32 integraHash, address executor) external;

Layer 4: Execution

IntegraExecutorV1

Source: src/execution/IntegraExecutorV1.sol

A gas abstraction layer that enables relayers to execute whitelisted operations on behalf of users. Implements nonce-based replay protection and fine-grained access control through (target, selector) pair whitelisting. Each forwarded call is limited to MAX_GAS_PER_OPERATION = 5,000,000 gas and return data is capped at MAX_RETURN_DATA_SIZE = 10,000 bytes.

IntegraForwarder

Source: src/forwarder/IntegraForwarder.sol

A thin wrapper over OpenZeppelin's ERC2771Forwarder that serves as the single trusted forwarder for all ERC-2771-compatible contracts in the protocol. Uses EIP-712 typed structured data signing with the domain name "IntegraForwarder".

The forwarding flow:

  1. User signs an EIP-712 ForwardRequest off-chain.
  2. Relayer calls execute(request, signature) on the forwarder.
  3. Forwarder validates signature, increments nonce, and forwards the call with the original sender appended to calldata.
  4. Target contract uses _msgSender() (from ERC2771Context) to extract the real sender.

Layer 5: Messaging

IntegraSignalV1

Source: src/messaging/IntegraSignalV1.sol

Token-to-token payment request messaging. Enables token holders to send encrypted payment requests to other token holders. Verification is performed against actual token ownership via the record's associated tokenizer. Payloads are encrypted with hybrid encryption (AES-256-GCM session key, per-party key wrapping). EAS attestations provide integrity guarantees for the payload hash.

Features:

  • Configurable timeouts (7--365 days) with extension mechanism (up to 180 days cumulative).
  • Three lifecycle states: PENDING, PAID, CANCELLED.
  • Process correlation via processHash.

IntegraMessageV1

Source: src/messaging/IntegraMessageV1.sol

A lightweight, event-sourced messaging system for workflow events. Uses Poseidon hashes for ID generation and requires Groth16 ZK proofs for anti-spam protection (proof of knowledge of the processHash). No state is stored on-chain -- all data is emitted as events for off-chain indexing.


Layer 6: Extensions

Source: src/extensions/

Extensions are composable building blocks for domain-specific record logic. They compose small, purpose-built building blocks via Solidity inheritance, keeping each component focused and auditable.

Extensions follow a strict 3-tier inheritance architecture:

Tier 1 -- Base + Building Blocks:

  • RecordExtension -- abstract base with IContractV2, auth helpers, IntegraRecord bridge, transfer policy bridge
  • CustodyChain -- physical custody transfer tracking
  • DataCommitment -- hash commitment and encrypted blob storage
  • FundsCustody -- ETH and ERC-20 fund escrow
  • PaymentLedger -- payment obligation tracking

Tier 2 -- Token Bindings + Diamond Resolvers:

  • TokenBinding721, TokenBinding1155, TokenBinding20 -- implement ITokenizerV1 lifecycle for each ERC standard
  • TokenizedExtension721, TokenizedExtension1155, TokenizedExtension20 -- resolve Solidity diamond inheritance between RecordExtension and TokenBinding*

Tier 3 -- Concrete Extensions:

  • OwnershipExtension -- simplest concrete extension, ERC-721 ownership proof
  • EscrowExtension -- full escrow lifecycle with DataCommitment + FundsCustody
  • ApprovalExtension -- multi-party document approval workflow

Layer 7: Lens

Source: src/lens/IntegraLens.sol

A stateless, permissionless view contract that composes IContractV2 responses from every contract attached to a record -- tokenizer, policy resolver, and resolvers -- into a single RecordView struct. One off-chain eth_call returns the complete state, all available actions with routing information, and transfer policy status.

IntegraLens holds no state of its own. All IContractV2 calls are wrapped in try/catch for fault tolerance -- if a contract does not implement IContractV2 or reverts, the Lens gracefully returns empty state for that source. A single eth_call to IntegraLens provides the complete record state, eliminating the need for off-chain state reconstruction logic.


Layer 8: Policy

Source: src/policy/

The policy layer introduces pluggable transfer restrictions for tokenized records. When a token is transferred peer-to-peer (not minted or burned), the tokenizer's _update hook calls the policy resolver to check whether the transfer is allowed, with a 100,000 gas cap.

The ITransferPolicy interface follows the ERC-1404 pattern:

  • checkTransfer() -- enforcement function called by tokenizer _update(). Reverts if transfer is not allowed.
  • detectTransferRestriction() -- pre-flight check returning a restriction code for UI display.
  • messageForTransferRestriction() -- human-readable message for a restriction code.

Concrete policies:

  • AttestationTransferPolicy -- requires parties to hold valid CORE_TRANSFER attestations via EAS, with bilateral and recipient-only modes. Implements IContractV2 for Lens visibility.
  • TransitionPolicy -- temporary policy for graceful migration between policy resolvers with a deadline-based grace period. Immutable parameters, no admin functions.

Layer 9: Interfaces

Source: src/interfaces/, src/tokenizers/interfaces/, src/resolvers/interfaces/

Four interfaces that provide universal introspection across the protocol:

InterfaceFunctionsPurpose
IContractV2getRecordState, getAvailableActions, stateSchemaUniversal state and action discovery for any contract
ITokenPartyisTokenHolderCross-standard token holder detection
ITokenizerV2ITokenizerV1 + IContractV2Composed interface for v1.7 tokenizers
IResolverV2IResolver + IContractV2Composed interface for v1.7 resolvers

IContractV2 is implemented by all tokenizers, both concrete resolvers, all extensions, and AttestationTransferPolicy. ITokenParty is implemented by all tokenizers via BaseTokenizerV1.

ITokenParty provides a single cross-standard query for token holder detection. Resolvers call ITokenParty(tokenizer).isTokenHolder(account) without knowing which token standard the tokenizer uses.


Layer 10: Tokenizers

Source: src/tokenizers/

23 concrete tokenizer contracts that represent different document rights and relationships. All tokenizers share a common inheritance hierarchy rooted at BaseTokenizerV1, which inherits from AttestationAccessControlV1.

Key design properties:

  • All tokenizers implement IContractV2 (directly or via ITokenizerV2) for universal state and action discovery.
  • All tokenizers implement ITokenParty via BaseTokenizerV1 for cross-standard holder detection.
  • Transfer policy enforcement is built into all token-standard base contracts via ITransferPolicy.checkTransfer() with a 100,000 gas cap.
  • BaseERC20TokenizerV1 provides both named and anonymous reservation support directly.

By Token Standard

ERC-721 (BaseERC721TokenizerV1 -- 1 token per record, 8 contracts):

  • OwnershipTokenizerV1 -- transferable single-owner representation
  • DebtTokenizerV1 -- debt instrument tokens
  • EscrowTokenizerV1 -- escrow-held document rights
  • InsuranceTokenizerV1 -- insurance policy tokens
  • InvoiceTokenizerV1 -- tokenized invoices
  • OptionsTokenizerV1 -- option contract tokens
  • SupplyChainTokenizerV1 -- supply chain provenance
  • VaultTokenizerV1 -- vault share tokens

Soulbound ERC-721 (BaseSoulboundERC721TokenizerV1 -- non-transferable, 3 contracts):

  • SoulboundTokenizerV1 -- non-transferable identity-bound token
  • LicenseTokenizerV1 -- license grant tokens
  • MembershipTokenizerV1 -- membership access tokens

ERC-1155 (BaseERC1155TokenizerV1 -- multiple tokens per record, 7 contracts):

  • AgreementTokenizerV1 -- three-party agreement records
  • BadgeTokenizerV1 -- achievement/credential badge
  • MultiPartyTokenizerV1 -- multi-party document participation
  • RentalTokenizerV1 -- time-bounded rental access
  • RoyaltyTokenizerV1 -- royalty share distribution
  • SemiFungibleTokenizerV1 -- mixed fungible/non-fungible positions
  • TrustTokenizerV1 -- trust relationship tokens

ERC-20 (BaseERC20TokenizerV1 -- fungible shares per record, 5 contracts):

  • FractionalTokenizerV1 -- fractional ownership shares
  • GovernanceTokenizerV1 -- governance voting tokens
  • SecurityTokenTokenizerV1 -- regulated security tokens
  • SharesTokenizerV1 -- equity share tokens
  • StreamTokenizerV1 -- payment stream tokens

Every tokenizer holds an immutable reference to IntegraRecordV1 and validates that the record uses this specific tokenizer before allowing any operation.


Layer 11: Resolvers

Source: src/resolvers/

Resolvers are pluggable on-chain logic modules that attach to records. Each record can bind up to 10 resolvers. Resolvers are organized into 5 categories (identified by bitmask):

CategoryBitmaskPurpose
Metadata1 << 0Read-heavy, describes the record
Gatekeeper1 << 1Blocking validation (can the transfer happen?)
Behavioral1 << 2Stateful lifecycle hooks and actions
Automation1 << 3Trigger conditions, permissionless evaluation
Integration1 << 4External system bridges

All resolvers inherit from BaseResolver, which provides ERC2771 meta-transaction support, AccessControl, Pausable, ReentrancyGuard, and the _isRecordParty() helper for party detection across owner, executor, and token holder (via ITokenParty).

All resolvers inherit directly from BaseResolver and implement the category interfaces they need (flat inheritance model).

Concrete Resolvers

ADRResolverV3 -- Alternative Dispute Resolution. A multi-category resolver (Behavioral + Gatekeeper + Automation) implementing a full arbitration lifecycle: arbitration clause anchoring, 11-state dispute state machine, evidence via hash anchors, multi-party settlement, deadline management, automated triggers (payment expiry, attestation expiry, custom evaluators), and EAS attestation integration. Implements IContractV2.

AAAResolverV1 -- Agreement-bound dispute resolution. Structurally similar to ADRResolverV3 but replaces the ArbitrationClause prerequisite with agreement binding via AgreementTokenizerV1. Disputes can be initiated when all three agreement tokens are claimed. Introduces the "commercial party" concept for settlement access control. Implements IContractV2.

Support Libraries

LibraryPurpose
StateMachineGeneric state lifecycle enforcement with bitmask-based two-tier transitions
MultiPartyConfirmationQuorum-based confirmation with nonce replay protection and cooldowns
HashAnchorAppend-only hash-based proof-of-existence list
DeadlineTrackerTime-based deadline management with absolute and relative modes
CounterPer-record keyed counter management
PeriodicSchedulePeriodic schedule management for recurring obligations
ProportionSplitBasis-point allocation and proportional calculation
VestingScheduleLinear vesting calculations

Contract Inventory

#ContractLayerSource
1IntegraExistenceV1Coresrc/registry/IntegraExistenceV1.sol
2IntegraLedgerV1Coresrc/core/registries/IntegraLedgerV1.sol
3IntegraRegistryV1Coresrc/core/registries/IntegraRegistryV1.sol
4CapabilityNamespaceV1Coresrc/core/capabilities/CapabilityNamespaceV1.sol
5AttestationAccessControlV1Access Controlsrc/core/access/AttestationAccessControlV1.sol
6EASAttestationProviderV1Access Controlsrc/core/providers/EASAttestationProviderV1.sol
7IntegraRecordV1Recordssrc/registry/IntegraRecordV1.sol
8IntegraExecutorV1Executionsrc/execution/IntegraExecutorV1.sol
9IntegraForwarderExecutionsrc/forwarder/IntegraForwarder.sol
10IntegraSignalV1Messagingsrc/messaging/IntegraSignalV1.sol
11IntegraMessageV1Messagingsrc/messaging/IntegraMessageV1.sol
12RecordExtensionExtensionssrc/extensions/RecordExtension.sol
13OwnershipExtensionExtensionssrc/extensions/OwnershipExtension.sol
14CustodyChainExtensionssrc/extensions/CustodyChain.sol
15DataCommitmentExtensionssrc/extensions/DataCommitment.sol
16FundsCustodyExtensionssrc/extensions/FundsCustody.sol
17PaymentLedgerExtensionssrc/extensions/PaymentLedger.sol
18TokenBinding721Extensionssrc/extensions/TokenBinding721.sol
19TokenBinding1155Extensionssrc/extensions/TokenBinding1155.sol
20TokenBinding20Extensionssrc/extensions/TokenBinding20.sol
21TokenizedExtension721Extensionssrc/extensions/TokenizedExtension721.sol
22TokenizedExtension1155Extensionssrc/extensions/TokenizedExtension1155.sol
23TokenizedExtension20Extensionssrc/extensions/TokenizedExtension20.sol
24EscrowExtensionExtensionssrc/extensions/EscrowExtension.sol
25ApprovalExtensionExtensionssrc/extensions/ApprovalExtension.sol
26IntegraLensLenssrc/lens/IntegraLens.sol
27ITransferPolicyPolicysrc/policy/ITransferPolicy.sol
28TransferRestrictionCodesPolicysrc/policy/TransferRestrictionCodes.sol
29AttestationTransferPolicyPolicysrc/policy/AttestationTransferPolicy.sol
30TransitionPolicyPolicysrc/policy/TransitionPolicy.sol
31IContractV2Interfacessrc/interfaces/IContractV2.sol
32ITokenPartyInterfacessrc/interfaces/ITokenParty.sol
33ITokenizerV2Interfacessrc/tokenizers/interfaces/ITokenizerV2.sol
34IResolverV2Interfacessrc/resolvers/interfaces/IResolverV2.sol
35BaseTokenizerV1Tokenizerssrc/tokenizers/abstract/BaseTokenizerV1.sol
36BaseERC721TokenizerV1Tokenizerssrc/tokenizers/abstract/BaseERC721TokenizerV1.sol
37BaseSoulboundERC721TokenizerV1Tokenizerssrc/tokenizers/abstract/BaseSoulboundERC721TokenizerV1.sol
38BaseERC1155TokenizerV1Tokenizerssrc/tokenizers/abstract/BaseERC1155TokenizerV1.sol
39BaseERC20TokenizerV1Tokenizerssrc/tokenizers/abstract/BaseERC20TokenizerV1.sol
40OwnershipTokenizerV1Tokenizerssrc/tokenizers/OwnershipTokenizerV1.sol
41DebtTokenizerV1Tokenizerssrc/tokenizers/DebtTokenizerV1.sol
42EscrowTokenizerV1Tokenizerssrc/tokenizers/EscrowTokenizerV1.sol
43InsuranceTokenizerV1Tokenizerssrc/tokenizers/InsuranceTokenizerV1.sol
44InvoiceTokenizerV1Tokenizerssrc/tokenizers/InvoiceTokenizerV1.sol
45OptionsTokenizerV1Tokenizerssrc/tokenizers/OptionsTokenizerV1.sol
46SupplyChainTokenizerV1Tokenizerssrc/tokenizers/SupplyChainTokenizerV1.sol
47VaultTokenizerV1Tokenizerssrc/tokenizers/VaultTokenizerV1.sol
48SoulboundTokenizerV1Tokenizerssrc/tokenizers/SoulboundTokenizerV1.sol
49LicenseTokenizerV1Tokenizerssrc/tokenizers/LicenseTokenizerV1.sol
50MembershipTokenizerV1Tokenizerssrc/tokenizers/MembershipTokenizerV1.sol
51AgreementTokenizerV1Tokenizerssrc/tokenizers/AgreementTokenizerV1.sol
52BadgeTokenizerV1Tokenizerssrc/tokenizers/BadgeTokenizerV1.sol
53MultiPartyTokenizerV1Tokenizerssrc/tokenizers/MultiPartyTokenizerV1.sol
54RentalTokenizerV1Tokenizerssrc/tokenizers/RentalTokenizerV1.sol
55RoyaltyTokenizerV1Tokenizerssrc/tokenizers/RoyaltyTokenizerV1.sol
56SemiFungibleTokenizerV1Tokenizerssrc/tokenizers/SemiFungibleTokenizerV1.sol
57TrustTokenizerV1Tokenizerssrc/tokenizers/TrustTokenizerV1.sol
58FractionalTokenizerV1Tokenizerssrc/tokenizers/FractionalTokenizerV1.sol
59GovernanceTokenizerV1Tokenizerssrc/tokenizers/GovernanceTokenizerV1.sol
60SecurityTokenTokenizerV1Tokenizerssrc/tokenizers/SecurityTokenTokenizerV1.sol
61SharesTokenizerV1Tokenizerssrc/tokenizers/SharesTokenizerV1.sol
62StreamTokenizerV1Tokenizerssrc/tokenizers/StreamTokenizerV1.sol
63BaseResolverResolverssrc/resolvers/base/BaseResolver.sol
64ADRResolverV3Resolverssrc/resolvers/adr/ADRResolverV3.sol
65AAAResolverV1Resolverssrc/resolvers/adr/AAAResolverV1.sol

Key Design Patterns

Immutable References

All inter-contract references are set at construction time and stored as immutable state variables. This eliminates an entire class of admin-key attacks -- there is no setRecord() or setRegistry() function that could redirect a tokenizer to a malicious record contract.

// In BaseTokenizerV1
IntegraRecordV1 public immutable RECORD;

// In AttestationAccessControlV1
CapabilityNamespaceV1 public immutable NAMESPACE;
IntegraRegistryV1 public immutable REGISTRY;

// In IntegraRecordV1
IntegraExistenceV1 public immutable EXISTENCE;
IntegraRegistryV1 public immutable REGISTRY;

// In IntegraLens
IntegraRecordV1 public immutable INTEGRA_RECORD;
IntegraRegistryV1 public immutable REGISTRY;

// In RecordExtension
IntegraRecordV1 public immutable RECORD;

The tradeoff is that upgrading a referenced contract requires deploying a new version of the referencing contract. This is intentional -- it forces explicit migration rather than silent reference swaps.

Progressive Ossification

The protocol's governance model follows a progressive ossification path:

BOOTSTRAP --> MULTISIG --> DAO --> OSSIFIED
StageControllerPurpose
BOOTSTRAPTeam EOARapid iteration during launch
MULTISIGGuardian multisig (e.g., 3-of-5)Shared custody, reduced single-point risk
DAOOn-chain governor (community)Decentralized governance
OSSIFIEDPermanently frozenGovernance model locked forever

AttestationAccessControlV1 accepts a _bootstrapGovernor at construction, granting it DEFAULT_ADMIN_ROLE and GOVERNOR_ROLE. Since DEFAULT_ADMIN_ROLE controls all other roles via OpenZeppelin AccessControl, governance transitions are performed by revoking roles from the current controller and granting them to the next entity. Ossification freezes the governance model (who governs) but does not freeze operational management -- the controlling entity retains the ability to rotate operator keys, update providers, and manage roles.

Zero-Trust Executor Access

Tokenizer operations use a zero-trust model with no global fallback roles:

  1. Owner -- always has full access to their records.
  2. Per-record executor -- a specific address authorized by the owner for a specific record.
  3. No one else -- there is no global admin, no protocol-wide operator, and no fallback that could bypass per-record authorization.

The requireOwnerOrExecutor modifier validates this on every operation:

modifier requireOwnerOrExecutor(bytes32 integraHash) {
    (address registeredTokenizer, address owner, address authorizedExecutor) =
        RECORD.getAccessInfo(integraHash);
    // Must be THIS tokenizer
    if (registeredTokenizer != address(this)) revert WrongTokenizer(...);
    // Must be owner OR authorized executor
    if (_msgSender() != owner && _msgSender() != authorizedExecutor) revert Unauthorized(...);
    _;
}

IContractV2 Universal Introspection

All protocol contracts -- tokenizers, resolvers, extensions, and policy contracts -- implement three uniform functions:

  • getRecordState(integraHash) -- ABI-encoded domain state snapshot
  • getAvailableActions(integraHash, caller) -- function selectors the caller can invoke
  • stateSchema() -- schema identifier string for decoding state

This eliminates the need for per-contract adapters in the service layer. IntegraLens composes these responses into a single RecordView for off-chain consumption.

Extension Composition

Extensions provide composable building blocks for domain-specific record logic:

  • Building blocks (50-150 lines each) provide a single capability (custody, commitments, fund management).
  • Token bindings implement ITokenizerV1 lifecycle for each ERC standard.
  • Diamond resolvers handle Solidity diamond inheritance between RecordExtension and TokenBinding*.
  • Concrete extensions compose building blocks and inherit from a diamond resolver or directly from RecordExtension.

Capability Bitmask Access Control

Instead of role-based access (where you either have a role or you do not), the protocol uses a 256-bit capability bitmask. Each bit represents a specific permission. Capabilities can be composed, delegated, and verified through attestations.

if (!NAMESPACE.hasCapability(grantedCapabilities, requiredCapability)) {
    revert NoCapability(user, contentHash, requiredCapability);
}

ERC-2771 Meta-Transactions for Gasless Operation

All user-facing contracts inherit ERC2771Context and accept the IntegraForwarder as their trusted forwarder. Users sign EIP-712 typed data off-chain; a relayer submits the signed request on-chain. All contracts resolve the diamond inheritance between Context and ERC2771Context in favor of ERC2771Context.

Provider Abstraction

Attestation verification is decoupled from specific attestation systems through the IAttestationProvider interface. The reference implementation uses EAS, but the protocol can support Verifiable Credentials, ZK proofs, DID-based systems, or any other attestation mechanism by deploying a new provider and registering it in IntegraRegistryV1.

Providers can be configured at two levels:

  • Default provider -- applies to all records unless overridden.
  • Per-record provider -- overrides the default for a specific record.

Data Flow

The lifecycle of a document in the Integra protocol follows this path:

sequenceDiagram
    participant User
    participant Existence as IntegraExistenceV1
    participant Record as IntegraRecordV1
    participant Registry as IntegraRegistryV1
    participant Tokenizer as Tokenizer (ERC-721/1155/20)
    participant Provider as EASAttestationProviderV1
    participant Policy as ITransferPolicy
    participant Lens as IntegraLens
    participant Resolver as Resolver

    Note over User: 1. Existence Registration
    User->>Existence: register(contentHash)
    Existence-->>User: timestamp

    Note over User: 2. Record Creation
    User->>Record: register(params, proof)
    Record->>Existence: _ensureExists(contentHash)
    Record->>Registry: validate tokenizer is registered
    Record->>Record: store record (owner, tokenizer, resolvers, policyResolver)
    Record->>Resolver: onRegistered() lifecycle hook

    Note over User: 3. Token Reservation
    User->>Tokenizer: reserveTokenAnonymous(integraHash, ...)
    Tokenizer->>Record: getAccessInfo(integraHash)
    Tokenizer->>Tokenizer: validate owner/executor authorization
    Tokenizer->>Tokenizer: reserve token slot

    Note over User: 4. Token Claim (attestation-gated)
    User->>Tokenizer: claimToken(integraHash, tokenId, attestationUID)
    Tokenizer->>Provider: verifyCapabilities(proof, user, contentHash, capability)
    Provider->>Provider: 13-step EAS verification
    Provider-->>Tokenizer: (verified, grantedCapabilities)
    Tokenizer->>Tokenizer: mint token to claimant

    Note over User: 5. Transfer (policy-gated)
    User->>Tokenizer: safeTransferFrom(from, to, tokenId)
    Tokenizer->>Record: getPolicyResolver(integraHash)
    Tokenizer->>Policy: checkTransfer(integraHash, from, to, amount)
    Tokenizer->>Tokenizer: execute transfer

    Note over User: 6. State Query (via Lens)
    User->>Lens: getRecordView(integraHash, caller)
    Lens->>Record: lookup tokenizer, resolvers, policyResolver
    Lens->>Tokenizer: getRecordState(), getAvailableActions()
    Lens->>Resolver: getRecordState(), getAvailableActions()
    Lens-->>User: RecordView (composed state + actions)

Solidity Version and Dependencies

DependencyVersion
Solidity0.8.28 (exact, not a range)
OpenZeppelin Contractsv5.x
EAS (external)Ethereum Attestation Service
ERC StandardsERC-20, ERC-721, ERC-1155, ERC-2771, ERC-2612, ERC-4906, ERC-5192, ERC-165

All contracts use pragma solidity 0.8.28; (not ^0.8.28), ensuring deterministic compilation. OpenZeppelin v5.x provides:

  • AccessControl -- role-based admin permissions
  • ReentrancyGuardTransient -- transient-storage-based reentrancy protection (EIP-1153)
  • Pausable -- emergency stop mechanism
  • ERC2771Context / ERC2771Forwarder -- meta-transaction support
  • ERC721, ERC1155, ERC20, ERC20Permit, ERC20Votes -- token standards
  • SafeERC20 -- safe token transfer wrappers

No contracts use proxy patterns or upgradeability. Every contract is deployed as a standalone, immutable instance.


Security

Integra v1.7 implements a defense-in-depth security architecture across its production Solidity 0.8.28 contracts. The protocol's security philosophy rests on five principles:

  1. Attestation-gated access. Every state-mutating operation on a record requires a cryptographically verified attestation from an authorized issuer. There are no backdoor admin overrides that bypass attestation verification.

  2. Progressive ossification. Governance authority follows a one-way ratchet from team control to permanent immutability (BOOTSTRAP -> MULTISIG -> DAO -> OSSIFIED). Each transition is irreversible and narrows the set of actors who can modify governance parameters.

  3. Immutability where it matters. Core references (EAS contract, capability namespace, registry) are set at construction time as Solidity immutable values. The existence registry (IntegraExistenceV1) is write-once with no admin functions. Contract bytecode hashes are captured at registration and verified on every lookup.

  4. Zero-trust execution. The executor (IntegraExecutorV1) operates on an explicit allowlist model -- target contracts AND function selectors must both be whitelisted. There are no global fallback roles. Per-record executors are scoped to individual records, not granted broad authority.

  5. Non-upgradeability by design. All contracts are non-upgradeable. There are no proxy patterns, no delegatecall to mutable implementations, and no storage layout concerns. Security comes from immutability; evolution comes from deploying new contracts and migrating records.

The protocol builds on OpenZeppelin v5.x for battle-tested primitives: AccessControl for role management, ReentrancyGuardTransient for EIP-1153 transient storage reentrancy protection, Pausable for emergency stops, SafeERC20 for token transfers, and ERC2771Context for meta-transaction support.


Attestation Verification

EASAttestationProviderV1 implements a 13-step verification chain that validates every attestation before granting capabilities. Each step targets a specific attack vector.

Step 1: Attestation Retrieval

attestation = EAS.getAttestation(attestationUID);

Fetches the attestation struct from the Ethereum Attestation Service contract. This is the raw data source for all subsequent checks.

Step 2: Existence Check

if (attestation.uid == bytes32(0)) {
    revert AttestationNotFound(attestationUID);
}

Prevents: Fabricated attestation UIDs. An attacker cannot submit a random bytes32 value and have it accepted -- the attestation must exist on-chain in the EAS contract.

Step 3: Revocation Check

if (attestation.revocationTime != 0) {
    revert AttestationRevoked(attestationUID, attestation.revocationTime);
}

Prevents: Use of revoked credentials. When an issuer revokes an attestation (e.g., after a key compromise or permission change), this step ensures the revoked attestation is immediately rejected. The check is first-class -- it runs before any data decoding.

Step 4: Expiration Check

if (attestation.expirationTime != 0 && attestation.expirationTime <= block.timestamp) {
    revert AttestationExpired(attestationUID, attestation.expirationTime);
}

Prevents: Use of expired credentials. Attestations can carry an expiration timestamp. Zero means no expiration. This prevents stale attestations from granting perpetual access after their intended validity window.

Step 5: Schema Validation

if (attestation.schema != ACCESS_CAPABILITY_SCHEMA) {
    revert InvalidSchema(attestation.schema, ACCESS_CAPABILITY_SCHEMA);
}

Prevents: Schema confusion attacks. An attacker might create an attestation using a different EAS schema that happens to decode to plausible-looking data. By pinning the schema UID at construction time, only attestations created under Integra's specific capability schema are accepted.

Step 6: Recipient Binding (Front-Running Protection)

if (attestation.recipient != user) {
    revert InvalidRecipient(attestation.recipient, user);
}

Prevents: Attestation theft and front-running. An attacker who observes a valid attestation UID in the mempool cannot use it -- the attestation is bound to a specific recipient address. This is the primary front-running defense for attestation-gated operations.

Step 7: Issuer Authorization

address issuer = _getActiveIssuer(contentHash);
if (attestation.attester != issuer) {
    revert InvalidIssuer(attestation.attester, issuer);
}

Prevents: Unauthorized attestation issuance. Only the explicitly authorized issuer for each record can create valid attestations. The _getActiveIssuer() function checks revocation status first -- if the issuer has been revoked, it returns address(0), causing all attestations from that issuer to fail immediately.

Step 8: Chain ID Validation (Cross-Chain Replay Prevention)

if (sourceChainId != block.chainid) {
    revert WrongChain(block.chainid, sourceChainId);
}

Prevents: Cross-chain replay attacks. An attestation created on Ethereum mainnet cannot be replayed on Arbitrum, Base, or any other chain. The chain ID is embedded in the attestation data and verified against the current block.chainid.

Step 9: EAS Contract Verification (EAS Spoofing Prevention)

if (sourceEASContract != address(EAS)) {
    revert WrongEASContract(address(EAS), sourceEASContract);
}

Prevents: EAS contract spoofing. An attacker could deploy a malicious EAS contract that returns fabricated attestation data. By verifying that the attestation was created on the specific EAS contract instance that the provider trusts, this step ensures attestation data integrity.

Step 10: Record Contract Binding (Contract Spoofing Prevention)

if (recordContract != msg.sender) {
    revert WrongRecordContract(msg.sender, recordContract);
}

Prevents: Cross-contract attestation reuse. An attestation issued for TokenizerA cannot be replayed against TokenizerB. The attestation data embeds the address of the specific contract it was issued for, and that address must match msg.sender (the tokenizer contract calling the provider). Note: msg.sender is correct here because tokenizers inherit from AttestationAccessControlV1, making msg.sender the tokenizer contract itself.

Step 11: Schema Version Validation

if (attestationVersion != SCHEMA_VERSION) {
    revert InvalidSchemaVersion(attestationVersion, SCHEMA_VERSION);
}

Prevents: Schema version mismatch. As the protocol evolves, the attestation schema may change. This step ensures attestations created under older schema versions are not accidentally accepted by newer provider versions, preventing data interpretation mismatches.

Step 12: Content Hash Binding

if (attestedHash != contentHash) {
    revert WrongRecord(attestedHash, contentHash);
}

Prevents: Cross-record attestation reuse. An attestation issued for Record A cannot be used to authorize operations on Record B. The content hash (integraHash) in the attestation data must match the record being operated on.

Step 13: Attestation Age Validation (Optional)

if (maxAttestationAge > 0) {
    if (issuedAt > block.timestamp) {
        revert InvalidIssuedAtTimestamp(issuedAt, block.timestamp);
    }
    uint256 age;
    unchecked { age = block.timestamp - issuedAt; }
    if (age > maxAttestationAge) {
        revert AttestationTooOld(issuedAt, maxAttestationAge);
    }
}

Prevents: Use of stale attestations and future-dated timestamps. When enabled (configurable by the governor, bounded between 1 hour and 365 days), this step enforces attestation freshness. It also rejects attestations with issuedAt timestamps in the future, which would indicate manipulation.


Access Control

Capability-Based Bitmask Permissions

CapabilityNamespaceV1 defines a 256-bit capability bitmask where each bit position represents a specific permission. Bit positions are permanent -- once defined, a bit position's meaning never changes across all contracts and time periods.

The namespace is organized into four tiers of 8 bits each (bits 0-31):

TierBitsCapabilities
Core0-7VIEW, CLAIM, TRANSFER, UPDATE, DELEGATE, REVOKE, RESERVED, ADMIN
Record (DOC)8-15SIGN, WITNESS, NOTARIZE, VERIFY, AMEND, ARCHIVE, RESERVED x2
Financial (FIN)16-23REQUEST_PAYMENT, APPROVE_PAYMENT, EXECUTE_PAYMENT, CANCEL_PAYMENT, WITHDRAW, DEPOSIT, RESERVED x2
Governance (GOV)24-31PROPOSE, VOTE, EXECUTE, VETO, DELEGATE_VOTE, RESERVED x3

The CORE_ADMIN bit (bit 7) acts as a superuser flag -- when set, hasCapability() returns true for any required capability:

function hasCapability(uint256 granted, uint256 required) external pure returns (bool) {
    return ((granted & CORE_ADMIN) != 0) || ((granted & required) == required);
}

Capabilities are composed via bitwise OR and checked via bitwise AND. This allows a single attestation to grant multiple permissions simultaneously, and a single check to require multiple permissions.

Progressive Ossification (Governance Model)

The protocol's governance model is designed around a progressive ossification path:

BOOTSTRAP -> MULTISIG -> DAO -> OSSIFIED

This is an operational governance model, not an on-chain state machine. AttestationAccessControlV1 accepts a _bootstrapGovernor address at construction, which receives DEFAULT_ADMIN_ROLE and GOVERNOR_ROLE. Governance transitions are performed by revoking roles from the current controller and granting them to the next:

StageControllerHow to Transition
BootstrapTeam EOARevoke team roles, grant to multisig
MultisigGuardian multisig (e.g., 3-of-5)Revoke multisig roles, grant to DAO
DAOOn-chain governor (community)Freeze role assignments

Since DEFAULT_ADMIN_ROLE controls all other roles via OpenZeppelin AccessControl, role transfers between stages are straightforward admin operations. The model ensures governance stability for legal record tokens while allowing the controlling entity to rotate compromised operator keys, update providers, and manage operational roles.

Zero-Trust Executor Model

IntegraRecordV1 implements per-record executor authorization rather than broad role-based access:

mapping(bytes32 => address) public recordExecutor;

Each record has at most one authorized executor. Executors are explicitly bound to specific records -- there is no global "executor" role that can operate on any record. Self-authorization is prevented:

if (executor == _msgSender()) revert CannotAuthorizeSelf(_msgSender());

On ownership transfer, the executor is automatically revoked (zero-trust model). The new owner must explicitly re-authorize an executor.

Role-Based Access in Tokenizers and Resolvers

Tokenizers use OpenZeppelin AccessControl with three tiers (inherited from AttestationAccessControlV1):

  • GOVERNOR_ROLE: System-wide configuration (provider management, fee parameters, pause/unpause)
  • OPERATOR_ROLE: Operational management (issuer configuration, batch operations)
  • EXECUTOR_ROLE: Record-level operations (whitelisted contract calls, record state changes)

Resolvers use a simpler model with only GOVERNOR_ROLE (for pause/unpause and configuration) and DEFAULT_ADMIN_ROLE (for role management).

Roles are enforced via the onlyRole modifier. The DEFAULT_ADMIN_ROLE follows the governance stage -- it transfers from bootstrap governor to multisig to DAO during progressive ossification.


Replay Protection

Role-Based Replay Prevention in IntegraExecutorV1

IntegraExecutorV1 prevents unauthorized replay through EXECUTOR_ROLE access control. Only addresses granted the executor role can call executeOperation(). Since each operation is a direct call (not a signed meta-transaction), replay is prevented by the standard Ethereum transaction model -- each transaction has a unique nonce at the protocol level. The role check ensures that only authorized relayers can submit operations, and the (target, selector) whitelist ensures they can only call approved functions.

Chain ID Validation in EASAttestationProviderV1

Step 8 of the 13-step verification (described above) prevents cross-chain replay by comparing sourceChainId against block.chainid. An attestation created on chain 1 (Ethereum mainnet) cannot be used on chain 42161 (Arbitrum).

Source EAS Contract Verification

Step 9 prevents EAS contract spoofing. Even on the same chain, an attestation from a different EAS deployment (e.g., a test instance or malicious clone) is rejected.

Contract Address Binding

Step 10 binds attestations to the specific calling contract. Even if an attacker deploys a contract at the same address on a different chain (CREATE2), the chain ID check (Step 8) would catch it.


Reentrancy Protection

ReentrancyGuardTransient on Critical Contracts

All state-mutating contracts inherit OpenZeppelin's ReentrancyGuardTransient, which uses EIP-1153 transient storage (TSTORE/TLOAD) for gas-efficient reentrancy guards. Transient storage is automatically cleared at the end of each transaction, eliminating the warm/cold storage cost differential of traditional reentrancy guards.

The nonReentrant modifier is applied to all state-mutating external functions on:

  • IntegraExecutorV1 (operation execution)
  • IntegraRecordV1 (record management)
  • All tokenizer contracts (reserve, claim, cancel, transfer operations)
  • All resolver contracts (state mutations)
  • All extension contracts (building block operations)
  • Token binding contracts (TokenBinding721, TokenBinding1155, TokenBinding20)

Custom Transient Storage Guard in AttestationAccessControlV1

AttestationAccessControlV1 implements a custom reentrancy guard that shares the same transient storage slot as OpenZeppelin's ReentrancyGuardTransient:

function _setReentrancyGuard(bool entered) private {
    assembly {
        tstore(0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00, entered)
    }
}

This design solves a specific problem: the requiresCapability modifier makes external calls to attestation providers before the function body executes. A standard nonReentrant modifier would only set the guard after modifier execution, leaving the external provider call unprotected.

By using OpenZeppelin's exact ERC-7201 namespaced storage slot, the custom guard and OpenZeppelin's nonReentrant share state. This means:

  • _reentrancyGuardEntered() (inherited from OpenZeppelin) reads the same slot this function writes to
  • Cross-function reentrancy between requiresCapability-guarded functions and nonReentrant-guarded functions is properly detected
  • A reentrant call from a malicious attestation provider into any protected function on the same contract will revert

The requiresCapabilityWithUID modifier provides the same protection for single-value proof variants.


Gas Limiting

Executor Gas Caps

IntegraExecutorV1 enforces two gas-related safety limits:

  • MAX_GAS_PER_OPERATION = 5,000,000 -- each forwarded call to a target contract is limited to 5M gas. This prevents a single operation from consuming an entire block's gas limit, which could be used as a griefing vector against the relayer.
  • MAX_RETURN_DATA_SIZE = 10,000 -- return data is capped at 10,000 bytes. This prevents a malicious target contract from returning an enormous payload that inflates gas costs for the executor transaction (since return data is copied to memory).

Transfer Policy Gas Cap

All token-standard base contracts (BaseERC721TokenizerV1, BaseERC1155TokenizerV1, BaseERC20TokenizerV1) call ITransferPolicy.checkTransfer() with a 100,000 gas cap during the _update hook. This prevents a malicious or buggy policy resolver from consuming unbounded gas during token transfers. If the policy call exceeds the gas limit, the transfer reverts.

ITokenParty Gas Limit

When resolvers call ITokenParty(tokenizer).isTokenHolder(account) for party detection via _isRecordParty() in BaseResolver, the call is made with an explicit 100,000 gas cap (_TOKEN_HOLDER_GAS_LIMIT):

try ITokenParty(tokenizer).isTokenHolder{gas: _TOKEN_HOLDER_GAS_LIMIT}(account) returns (bool holds) {

If the isTokenHolder call exceeds the gas limit or reverts for any reason, the try/catch block handles the failure gracefully (the account is not treated as a token holder). This prevents a malicious or buggy tokenizer from consuming unbounded gas during party detection.

Resolver Gas Limits

IntegraRecordV1 calls resolver lifecycle hooks (onRegistered, onTransferred, onTokenizerAssociated) with gas-limited, non-blocking calls. A failing resolver does not block the operation -- it emits a ResolverNotificationFailed event. Gatekeeper resolver validation via canOwn() is also gas-bounded; a broken or unavailable resolver is skipped with a ResolverValidationFailed event.

Condition Evaluator Gas Cap

The ADR and AAA resolvers call custom IConditionEvaluator implementations with a gas cap of EVALUATOR_GAS_LIMIT = 50,000. This prevents griefing via expensive or infinite-loop evaluators.


Transfer Policy Security

Enforcement vs. Pre-Flight Separation

The ITransferPolicy interface separates enforcement from pre-flight validation:

  • checkTransfer() is called on-chain by the tokenizer during every peer-to-peer transfer. It must revert if the transfer is not allowed. It is called as a staticcall (no state changes).
  • detectTransferRestriction() is called off-chain by wallets and UIs before submitting a transaction. It returns a restriction code without reverting.

This separation ensures that on-chain enforcement cannot be bypassed by pre-flight results, and that pre-flight checks do not modify state.

Scope Limitation

Mint (from == address(0)) and burn (to == address(0)) are NOT subject to transfer policy. Minting is gated by the attestation/capability system via claimToken. Only peer-to-peer transfers (from != 0 && to != 0) invoke the policy resolver. This prevents policy contracts from blocking protocol-level operations.

Policy Resolver Validation

Policy resolvers must be registered and active in IntegraRegistryV1 before they can be attached to a record via setPolicyResolver(). The registry's code-hash integrity check ensures the policy contract has not been redeployed behind a proxy. Setting the policy resolver to address(0) removes the policy (unrestricted transfers).

TransitionPolicy Design

The TransitionPolicy contract is designed for safe migration between policy resolvers. It has:

  • Immutable parameters -- predecessor, successor, and deadline are set at construction and cannot be changed.
  • No admin functions -- there is no governor, no pause, no configuration.
  • Self-limiting behavior -- during the grace period, it allows transfers if either the predecessor or successor policy allows them. After the deadline, only the successor is checked.

Resolver Validation

Fail-Open vs. Fail-Closed Design

Resolver interactions follow a deliberate fail-open / fail-closed split:

Fail-open (non-blocking notifications): Lifecycle hooks (onRegistered, onTransferred, onTokenizerAssociated) are called with gas-limited, non-blocking calls. If a resolver reverts or runs out of gas, the operation proceeds and a ResolverNotificationFailed event is emitted. This prevents a broken resolver from permanently blocking record operations.

Fail-closed (blocking validation): Gatekeeper resolvers (canOwn, canTokenize) can actively reject operations by returning (false, reason). An active rejection (the call succeeds and the resolver returns false) reverts the transfer. However, if the resolver itself reverts or is unavailable, the validation is skipped with a ResolverValidationFailed event -- this is a fail-open for resolver failures, fail-closed for resolver rejections.

This design balances safety (resolvers can block invalid transfers) with liveness (broken resolvers cannot permanently lock records).

_isRecordParty in BaseResolver

BaseResolver provides _isRecordParty(integraHash, account) for cross-cutting party detection. It checks three conditions:

  1. Is the account the record owner? (via INTEGRA_RECORD.getOwner())
  2. Is the account an authorized executor? (via INTEGRA_RECORD.isAuthorizedExecutor())
  3. Is the account a token holder? (via ITokenParty(tokenizer).isTokenHolder())

This centralizes party detection logic, ensuring consistent behavior across all resolvers.


Emergency Controls

Pause/Unpause Mechanisms

All critical contracts implement OpenZeppelin's Pausable with whenNotPaused guards on state-mutating external functions. Pause authority is restricted to GOVERNOR_ROLE:

function pause() external virtual onlyRole(GOVERNOR_ROLE) { _pause(); }
function unpause() external virtual onlyRole(GOVERNOR_ROLE) { _unpause(); }

When paused:

  • EASAttestationProviderV1.verifyCapabilities() reverts, blocking all attestation-gated operations
  • IntegraExecutorV1.executeOperation() reverts, blocking all executor operations
  • Record registration and state changes are blocked
  • All tokenizer operations (including standard ERC transfers) are blocked
  • Extension operations are blocked

View functions remain accessible during pause to allow monitoring and diagnostics.

Emergency Fund Withdrawal

AttestationAccessControlV1 provides an emergency withdrawal function for accidentally sent ETH or tokens:

function emergencyWithdraw(address token, address to, uint256 amount)
    external onlyRole(GOVERNOR_ROLE)
{
    if (to == address(0)) revert ZeroAddress();
    if (token == address(0)) {
        (bool success, ) = to.call{value: amount}("");
        if (!success) revert ETHTransferFailed();
    } else {
        IERC20(token).safeTransfer(to, amount);
    }
}

Uses SafeERC20 for robust token handling across all chains and token implementations.

Time-Limited Emergency Override (180 Days)

IntegraRecordV1 implements a time-gated emergency authority:

uint256 public constant EMERGENCY_PERIOD = 180 days;
address public immutable EMERGENCY_ADDRESS;
uint256 public immutable EMERGENCY_EXPIRY;

The emergency address and expiry timestamp are both immutable -- set at construction and never modifiable. During the emergency period (first 180 days after deployment), the emergency address OR the governor can perform emergency operations. After expiry, only governance can act:

function _requireEmergencyAuth() internal view {
    if (block.timestamp < EMERGENCY_EXPIRY) {
        if (_msgSender() != EMERGENCY_ADDRESS && !hasRole(GOVERNOR_ROLE, _msgSender())) {
            revert UnauthorizedEmergencyUnlock(_msgSender());
        }
    } else {
        if (!hasRole(GOVERNOR_ROLE, _msgSender())) {
            revert EmergencyPowersExpired();
        }
    }
}

Emergency powers include:

  • Emergency unlock of resolver configurations
  • Emergency disable/re-enable of registration fees

All emergency actions require a justification string and emit detailed events including whether the emergency address was used and whether it was within the active period.

This provides a safety net during initial deployment while ensuring emergency powers are automatically revoked after the protocol stabilizes.


Immutability Guarantees

Write-Once Registry (IntegraExistenceV1)

IntegraExistenceV1 is a minimal, purpose-built contract with no admin functions, no upgradeability, no roles, and no pause mechanism. It provides a single guarantee: once a content hash is registered, it cannot be re-registered, modified, or deleted.

function register(bytes32 contentHash) external returns (uint64 timestamp) {
    if (contentHash == bytes32(0)) revert InvalidContentHash();
    ExistenceRecord storage record = records[contentHash];
    if (record.exists) {
        revert AlreadyExists(contentHash, record.registeredAt, record.registeredBy);
    }
    // ... write once, then immutable
}

The AlreadyExists error returns the original registration timestamp and registrant, providing full provenance. The contract has no selfdestruct, no owner, and no governance -- it is maximally immutable.

Batch registration (registerBatch) skips already-registered hashes rather than reverting, enabling idempotent bulk operations.

Immutable Contract References

Critical cross-contract references are declared as Solidity immutable, meaning they are embedded in the contract bytecode at deployment and can never be changed:

ContractImmutable References
AttestationAccessControlV1NAMESPACE, REGISTRY
EASAttestationProviderV1EAS, ACCESS_CAPABILITY_SCHEMA, NAMESPACE, RECORD
IntegraRecordV1EXISTENCE, REGISTRY, EMERGENCY_ADDRESS, EMERGENCY_EXPIRY
BaseTokenizerV1RECORD
RecordExtensionRECORD
IntegraLensINTEGRA_RECORD, REGISTRY
AttestationTransferPolicyPROVIDER, RECORD

This prevents governance from redirecting contract calls to malicious implementations after deployment. The trust model is established at construction time and cannot be subverted.

Code Hash Integrity Checking (IntegraRegistryV1)

IntegraRegistryV1 captures the extcodehash of every component at registration time:

bytes32 codeHash;
assembly {
    codeHash := extcodehash(componentAddress)
}

On every subsequent lookup via getComponent(), the current code hash is compared against the stored hash:

bytes32 currentHash;
assembly {
    currentHash := extcodehash(componentAddr)
}
if (currentHash != info.codeHash) return address(0);

If the code hash has changed (which should be impossible for non-upgradeable contracts, but could occur if a proxy's implementation is swapped), the registry returns address(0) -- effectively deactivating the component. This provides a runtime integrity check against implementation changes in registered components.


Privacy Design

Hash-Based Identity Metadata

The EAS attestation schema stores identity information as a hash, not as plaintext:

string public constant SCHEMA_DEFINITION =
    "bytes32 integraHash,"
    "uint256 tokenId,"
    "uint256 capabilities,"
    "bytes32 identityMetadataHash,"  // Hash only -- no plaintext identity data
    "uint256 sourceChainId,"
    "address sourceEASContract,"
    "address recordContract,"
    "uint64 issuedAt,"
    "bytes32 attestationVersion";

The identityMetadataHash field contains a hash of the identity metadata, not the metadata itself. The actual identity information is stored off-chain and verified off-chain. The on-chain hash provides a commitment that can be verified without revealing the underlying data.

Store Commitments, Not Plaintext

The extension building block DataCommitment follows the same principle -- it stores hashes and encrypted blobs, never cleartext domain data. The FundsCustody building block stores fund amounts and addresses but not the purpose or parties involved in the transaction. Sensitive domain data is always encrypted client-side before being stored on-chain.

No Plaintext Sensitive Data On-Chain

The protocol's privacy model follows a consistent pattern: on-chain storage contains hashes and encrypted blobs; plaintext sensitive data is never written to the blockchain. This is enforced by schema design (hash fields for identity metadata), by building block design (DataCommitment stores encrypted blobs), and by convention (encrypted payloads for messaging and resolver data).


Front-Running Protection

Recipient Matching in Attestation Verification

Step 6 of the 13-step verification binds attestations to a specific recipient address:

if (attestation.recipient != user) {
    revert InvalidRecipient(attestation.recipient, user);
}

This is the primary front-running defense. If an attacker observes a valid attestation UID in the mempool, they cannot use it because:

  1. The attestation is bound to a specific recipient address
  2. The user parameter comes from _msgSender() in the calling modifier
  3. The attacker's address will not match the intended recipient

Nonce-Based Ordering in Executor

IntegraExecutorV1 nonces provide ordering guarantees. Each address has a monotonically increasing nonce counter. Meta-transactions signed with a specific nonce can only be executed in order, preventing transaction reordering attacks.

Per-Record Case Reference Uniqueness (ADR/AAA Resolvers)

In the dispute resolution system, case references are globally unique. This prevents duplicate dispute filings. Case references should be submitted promptly to minimize the window for front-running.


Meta-Transaction Security

ERC-2771 Sender Extraction

All user-facing contracts inherit ERC2771Context and resolve _msgSender() to extract the real sender from forwarded calldata. The diamond inheritance between Context (from AccessControl/Pausable) and ERC2771Context is resolved in favor of ERC2771Context:

function _msgSender() internal view override(Context, ERC2771Context) returns (address) {
    return ERC2771Context._msgSender();
}

Governance Functions Use msg.sender

Some governance-level functions (ossification transitions, emergency controls) use msg.sender rather than _msgSender(). This is intentional -- governance operations should come from the multisig/DAO directly, not through meta-transaction relayers. This prevents a compromised relayer from initiating governance transitions.

Trusted Forwarder Validation

Only the IntegraForwarder contract is accepted as a trusted forwarder. The forwarder address is set at construction time (immutable in ERC2771Context). The forwarder validates EIP-712 signatures, checks deadlines, and consumes nonces before forwarding calls.


Extension Security

Building Block Isolation

Each extension building block (CustodyChain, DataCommitment, FundsCustody, PaymentLedger) is 50-150 lines of code, auditable in isolation. Building blocks define abstract hooks for authorization (_requireOwnerOrExecutor, _verifyClaimCapability) that RecordExtension implements. The building block never makes authorization decisions itself -- it delegates to the base.

Token Binding Auth Delegation

TokenBinding721, TokenBinding1155, and TokenBinding20 implement the ITokenizerV1 lifecycle but delegate all authorization checks to abstract hooks that RecordExtension provides. This ensures that token lifecycle operations (reserve, claim, cancel) are always gated by the same attestation-based access control used by the rest of the protocol.

Diamond Inheritance Resolution

TokenizedExtension721, TokenizedExtension1155, and TokenizedExtension20 resolve the Solidity diamond inheritance between RecordExtension and the corresponding TokenBinding*. This resolution is done once per token standard, eliminating diamond boilerplate from concrete extensions.


Integration Security Checklist

When integrating with Integra v1.7, follow these practices:

Attestation Handling

  • Always verify attestation freshness. If your use case is time-sensitive, configure maxAttestationAge on the EAS provider. The default is 0 (no limit), which may not be appropriate for high-value operations.
  • Do not cache attestation UIDs across sessions. Attestations can be revoked at any time. Always submit the UID for on-chain verification.
  • Use per-record providers when appropriate. The setRecordProvider() function allows overriding the default attestation provider for specific records. Use this for records that require different verification levels.
  • Handle all 13 revert reasons. Each step in the verification chain has a specific custom error. Decode these for user-facing error messages rather than showing generic "transaction failed."

Executor Integration

  • Use per-record executors, not broad permissions. Authorize executors per-record via IntegraRecordV1.authorizeExecutor(). Avoid granting EXECUTOR_ROLE broadly unless the address genuinely needs system-wide authority.
  • Whitelist specific target-selector pairs. Use setTargetSelectorAllowed() for the narrowest possible permission surface.
  • Handle ExecutionFailed reverts. Parse the returnData bytes in ExecutionFailed errors to extract the underlying revert reason from the target contract.

Transfer Policy Considerations

  • Pre-flight before submitting transfers. Call detectTransferRestriction() off-chain before submitting a transfer transaction. This avoids wasted gas on transfers that will be rejected by policy.
  • Use IntegraLens for composed pre-flight. IntegraLens.canTransfer() wraps detectTransferRestriction and messageForTransferRestriction into a single call.
  • Handle the 100,000 gas cap. Custom policy resolvers must complete their checkTransfer() logic within 100,000 gas. Exceeding this limit will cause the transfer to revert.
  • Understand mint/burn scope exclusion. Transfer policies are only enforced on peer-to-peer transfers. Minting and burning are not subject to policy checks.

Meta-Transaction Considerations

  • Use _msgSender() consistently. All Integra contracts use ERC2771Context._msgSender() for caller identification. If you build contracts that interact with Integra, follow the same pattern.
  • Be aware that governance functions use msg.sender. Governance operations should come from the multisig/DAO directly, not through meta-transaction relayers.

Record Management

  • Check EMERGENCY_EXPIRY before relying on emergency authority. The 180-day emergency period is a deployment-time parameter. Verify the current state via getEmergencyStatus().
  • Verify code hash integrity. When resolving components from IntegraRegistryV1, a getComponent() return of address(0) may indicate a code hash mismatch, not just an unregistered component.
  • Handle the pause state. All state-mutating operations revert when paused. Check the pause state before submitting transactions to avoid wasted gas.

Token Operations

  • Always use SafeERC20 for token transfers. Integra contracts use OpenZeppelin's SafeERC20 wrapper. If your integration transfers tokens to or from Integra contracts, use the same pattern.
  • Respect the whenNotPaused guard. Token operations on all tokenizers are gated by pause state.
  • Verify record ownership before executor authorization. Only record owners can authorize executors. Verify ownership via IntegraRecordV1.getOwner() before attempting executor setup.

IContractV2 Integration

  • Use IntegraLens for composed state queries. Rather than calling IContractV2 on individual contracts, use IntegraLens.getRecordView() for a single composed view of all state and actions.
  • Handle try/catch gracefully. IntegraLens wraps all IContractV2 calls in try/catch. If a contract does not implement IContractV2 or reverts, the Lens returns empty state for that source.
  • Decode state using stateSchema. Each contract's stateSchema() returns an identifier string used to select the correct ABI decoder for getRecordState() return values.

Security Contact

Report security vulnerabilities to security@integraledger.com.

For responsible disclosure, please include:

  • Affected contract(s) and function(s)
  • Description of the vulnerability
  • Steps to reproduce or proof of concept
  • Estimated severity and impact

Do not disclose vulnerabilities publicly before coordinated remediation.

On this page

System OverviewLayer DescriptionsLayer 1: CoreIntegraExistenceV1IntegraLedgerV1IntegraRegistryV1CapabilityNamespaceV1Layer 2: Access ControlAttestationAccessControlV1EASAttestationProviderV1Layer 3: RecordsIntegraRecordV1Layer 4: ExecutionIntegraExecutorV1IntegraForwarderLayer 5: MessagingIntegraSignalV1IntegraMessageV1Layer 6: ExtensionsLayer 7: LensLayer 8: PolicyLayer 9: InterfacesLayer 10: TokenizersBy Token StandardLayer 11: ResolversConcrete ResolversSupport LibrariesContract InventoryKey Design PatternsImmutable ReferencesProgressive OssificationZero-Trust Executor AccessIContractV2 Universal IntrospectionExtension CompositionCapability Bitmask Access ControlERC-2771 Meta-Transactions for Gasless OperationProvider AbstractionData FlowSolidity Version and DependenciesSecurityAttestation VerificationStep 1: Attestation RetrievalStep 2: Existence CheckStep 3: Revocation CheckStep 4: Expiration CheckStep 5: Schema ValidationStep 6: Recipient Binding (Front-Running Protection)Step 7: Issuer AuthorizationStep 8: Chain ID Validation (Cross-Chain Replay Prevention)Step 9: EAS Contract Verification (EAS Spoofing Prevention)Step 10: Record Contract Binding (Contract Spoofing Prevention)Step 11: Schema Version ValidationStep 12: Content Hash BindingStep 13: Attestation Age Validation (Optional)Access ControlCapability-Based Bitmask PermissionsProgressive Ossification (Governance Model)Zero-Trust Executor ModelRole-Based Access in Tokenizers and ResolversReplay ProtectionRole-Based Replay Prevention in IntegraExecutorV1Chain ID Validation in EASAttestationProviderV1Source EAS Contract VerificationContract Address BindingReentrancy ProtectionReentrancyGuardTransient on Critical ContractsCustom Transient Storage Guard in AttestationAccessControlV1Gas LimitingExecutor Gas CapsTransfer Policy Gas CapITokenParty Gas LimitResolver Gas LimitsCondition Evaluator Gas CapTransfer Policy SecurityEnforcement vs. Pre-Flight SeparationScope LimitationPolicy Resolver ValidationTransitionPolicy DesignResolver ValidationFail-Open vs. Fail-Closed Design_isRecordParty in BaseResolverEmergency ControlsPause/Unpause MechanismsEmergency Fund WithdrawalTime-Limited Emergency Override (180 Days)Immutability GuaranteesWrite-Once Registry (IntegraExistenceV1)Immutable Contract ReferencesCode Hash Integrity Checking (IntegraRegistryV1)Privacy DesignHash-Based Identity MetadataStore Commitments, Not PlaintextNo Plaintext Sensitive Data On-ChainFront-Running ProtectionRecipient Matching in Attestation VerificationNonce-Based Ordering in ExecutorPer-Record Case Reference Uniqueness (ADR/AAA Resolvers)Meta-Transaction SecurityERC-2771 Sender ExtractionGovernance Functions Use msg.senderTrusted Forwarder ValidationExtension SecurityBuilding Block IsolationToken Binding Auth DelegationDiamond Inheritance ResolutionIntegration Security ChecklistAttestation HandlingExecutor IntegrationTransfer Policy ConsiderationsMeta-Transaction ConsiderationsRecord ManagementToken OperationsIContractV2 IntegrationSecurity Contact