Integrate17

Solidity

On-chain, CompliR is one interface and one require. AttestationRegistry stores the latest tier per address with its expiry and the decision hash; your contract asks isAllowed before it moves funds. The registry is live on Arc mainnet at 0xA82EDb5e111c31C63E06EF0007f2fa1a9e7EB30d (verified on Sourcify); the code below is what it answers.

ICompliR

solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/// Minimal read interface every integrator needs. Copy this file (contracts/src/ICompliR.sol).
interface ICompliR {
    enum Tier { UNKNOWN, LOW, MEDIUM, HIGH }          // numeric values are part of the ABI

    event Attested(address indexed subject, Tier tier, uint64 validUntil, bytes32 decisionHash);
    event TierChanged(address indexed subject, Tier from, Tier to);  // also on revoke
    event Anchored(bytes32 indexed root, uint256 fromSeq, uint256 toSeq);

    /// Raw stored tier. UNKNOWN when never attested or revoked; validUntil 0 then. Check validUntil yourself.
    function tierOf(address subject) external view returns (Tier tier, uint64 validUntil, bytes32 decisionHash);

    /// true iff tier != UNKNOWN && tier <= maxTier && validUntil > block.timestamp
    function isAllowed(address subject, Tier maxTier) external view returns (bool);
}
Tier values
UNKNOWN 0 · LOW 1 · MEDIUM 2 · HIGH 3 - mirrored in src/lib/types.ts TIER_ONCHAIN
decisionHash
keccak256 of the canonical decision JSON; the same value sits in the signed attestation and the ledger entry
Registry
AttestationRegistry.sol: Ownable2Step + Pausable, attester role, EIP-712 attest (permissionless, signature is the authority), attestDirect / attestBatch (attester), revoke, anchorRoot (onlyAttester)

The one-line integration

solidity
require(ICompliR(COMPLIR_REGISTRY).isAllowed(payee, ICompliR.Tier.MEDIUM), "CompliR: payee tier too high");

That is the whole integration. It fails closed: an address that was never screened is UNKNOWN, an attestation past validUntil is stale, and both revert before any transfer. Pair it with a watch so a tier change refreshes the registry before your next payout, or read tierOf and apply your own rule.

GuardedTreasury example

solidity
// contracts/src/examples/GuardedTreasury.sol - the one-line integration as a complete example
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ICompliR} from "../ICompliR.sol";

contract GuardedTreasury is Ownable {
    using SafeERC20 for IERC20;

    ICompliR public immutable compliR;
    IERC20 public immutable token;                       // USDC on Arc: 0x3600…0000 (6 dp)
    ICompliR.Tier public maxTier = ICompliR.Tier.MEDIUM; // HIGH is never paid

    event Released(address indexed payee, uint256 amount, bytes32 decisionHash);
    event MaxTierChanged(ICompliR.Tier from, ICompliR.Tier to);

    constructor(address registry, address token_, address initialOwner) Ownable(initialOwner) {
        compliR = ICompliR(registry);
        token = IERC20(token_);
    }

    /// An agent proposes, the contract decides.
    function release(address payee, uint256 amount) external onlyOwner {
        require(compliR.isAllowed(payee, maxTier), "CompliR: payee tier too high");
        (,, bytes32 decisionHash) = compliR.tierOf(payee);
        token.safeTransfer(payee, amount);
        emit Released(payee, amount, decisionHash);
    }

    function setMaxTier(ICompliR.Tier newMax) external onlyOwner {
        require(newMax != ICompliR.Tier.UNKNOWN, "GuardedTreasury: UNKNOWN never pays");
        emit MaxTierChanged(maxTier, newMax);
        maxTier = newMax;
    }

    function withdraw(address to, uint256 amount) external onlyOwner {
        token.safeTransfer(to, amount);
    }
}

This is the contract behind Guarded payouts. Deployment, funding and the Foundry scripts are in contracts/README.md; gas on Arc is USDC and the broadcaster needs a USDC balance.

Develop

sh
cd contracts
forge build && forge test -vv            # 45 tests, incl. typehash parity with the TypeScript signer
forge build && node scripts/export-abi.mjs   # → src/lib/contracts/{registryAbi,guardedTreasuryAbi}.ts

Dependencies are vendored (forge-std, OpenZeppelin 5.4.0); no submodules. evm_version = paris keeps the bytecode runnable on any post-Merge EVM until Arc's fork is pinned. Verified deploy addresses will appear on Welcome and at /api/v1/health.