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

Mental model

  • Predicated functions on your contract require a PredicateMessage, an attestation produced by the PredicateAPI.
  • The contract, via PredicateClient, verifies the attestation onchain before executing business logic.
  • If the message is valid and unexpired for the exact call, execution proceeds; otherwise the transaction reverts.

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.
To install the Predicate Contracts, run one of the following commands:
npm i @predicate/contracts
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 setPolicy 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);
    }

    // Predicated 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 deployment script to include the following constructor arguments:
  • _predicateManager: The ServiceManager address for your target chain. See Supported Chains.
  • _policyID: The policy identifier. For initial testing, you may use x-test-random, which returns a compliant response approximately 50% of the time. You will update this value in step two.

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