Smart Contracts

Integrating Predicate on the smart contract level ensures no transaction is processed without a direct compliance attestation.
1

Select integration pattern

Based off your application, choose between two smart contract integration patterns
2

Code changes

While every application is different, you will find an example contract integration below
3

Contract deployment

Optionally, modify existing deployment scripts to include the addition of new state variables

Integration Pattern

There are two recommended integration patterns.
Both integration patterns leverage PredicateClient.sol, an audited, OpenZeppelin-namespaced smart contract we provide to simplify onchain integration.
Recommended when users don’t originally directly interact with your smart contract (e.g., Uniswap V4 hooks, batch transactions). Your application contract inherits the PredicateClient contract directly allowing you to enforce attestations directly. This is the simplest integration as no additional smart contract deployment is required.Application Contract ExampleYou will need to implement the setPolicyID and setPredicateManager functions which we recommend you make ownable.
Example Contract Direct Inheritance Pattern
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {PredicateClient} from "../lib/predicate-contracts/src/mixins/PredicateClient.sol";
import {PredicateMessage} from "../lib/predicate-contracts/src/interfaces/IPredicateClient.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

contract MetaCoin is PredicateClient, Ownable {
    mapping(address => uint256) public balances;
    
    event Transfer(address indexed _from, address indexed _to, uint256 _value);

    constructor(address _owner, address _predicateManager, string memory _policyID) Ownable(_owner) {
        balances[_owner] = 10_000_000_000_000;
        _initPredicateClient(_predicateManager, _policyID);
    }

    // Protected function that requires predicate validation
    function sendCoin(address _receiver, uint256 _amount, PredicateMessage calldata _message) external payable {
        bytes memory encodedSigAndArgs = abi.encodeWithSignature("_sendCoin(address,uint256)", _receiver, _amount);
        require(
            _authorizeTransaction(_message, encodedSigAndArgs, msg.sender, msg.value),
            "MetaCoin: unauthorized transaction"
        );
        
        _sendCoin(_receiver, _amount);
    }

    function _sendCoin(address _receiver, uint256 _amount) internal {
        require(balances[msg.sender] >= _amount, "MetaCoin: insufficient balance");
        balances[msg.sender] -= _amount;
        balances[_receiver] += _amount;
        emit Transfer(msg.sender, _receiver, _amount);
    }

    // Admin functions - implement these with proper access control
    function setPolicy(string memory _policyID) external onlyOwner {
        _setPolicy(_policyID);
    }

    function setPredicateManager(address _predicateManager) external onlyOwner {
        _setPredicateManager(_predicateManager);
    }
}

Deploying the Contracts

Update your existing deploy script to include the additional constructor arguments:
  • _predicateManager: The ServiceManager address for your target chain
  • _policyID: Your policy identifier (leave this blank, you’ll set it in step 2)

Next Step: After deploying your contracts, proceed to Application Onboarding to configure your organization and policies.