Skip to main content
This guide will walk you through how to integrate your smart contract with the Forte Rules Engine. For this guide, we will use the following scenario: You are releasing an NFT series using the ERC-721 standard, using your contract ClaireNFT.sol. You have a list of people who are allowed to mint stored in an on-chain oracle, along with how many they’re each allowed to mint. However, the owner of the NFT contract (you) should be able to bypass this limitation. These minting restrictions will be enforced with the Rules Engine.

Policy Definition

Now, we will create our policy.json file that defines our Policy. We have pre-created it for you here to match the described scenario. For more information on how to configure policies yourself, check out the Policy Templates or the Policy Syntax Guide.
policy.json
{
  "Policy": "ClaireNFT Policy",
  "Description": "Manages the allowlist for this NFT project",
  "PolicyType": "closed",
  "CallingFunctions": [
    {
      "Name": "Mint",
      "FunctionSignature": "mint(address to, uint256 quantity)",
      "EncodedValues": "address to, uint256 quantity, address owner"
    }
  ],
  "ForeignCalls": [
    {
      "Name": "GetAllowedMintAmount",
      "Address": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e",
      "Function": "allowedMintAmount(address)",
      "ReturnType": "uint256",
      "ValuesToPass": "to",
      "MappedTrackerKeyValues": "",
      "CallingFunction": "Mint"
    },
    {
      "Name": "DecrementAllowedMintAmount",
      "Address": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e",
      "Function": "removeOneMint(address)",
      "ReturnType": "bool",
      "ValuesToPass": "to",
      "MappedTrackerKeyValues": "",
      "CallingFunction": "Mint"
    }
  ],
  "Trackers": [],
  "MappedTrackers": [],
  "Rules": [
    {
      "Name": "Mint restrictions",
      "Description": "Limits mints",
      "Condition": "FC:GetAllowedMintAmount > 0 OR to == owner",
      "PositiveEffects": ["emit NFTMinted"],
      "NegativeEffects": ["revert(\"Address not allowed to mint.\")"],
      "CallingFunction": "Mint"
    },
    {
      "Name": "Decrement mint amount",
      "Description": "Decrements allowed mint amount",
      "Condition": "FC:GetAllowedMintAmount > 0",
      "PositiveEffects": ["FC:DecrementAllowedMintAmount"],
      "NegativeEffects": [],
      "CallingFunction": "Mint"
    }
  ]
}
Notice that the function calling the policy is mint(address to, uint256 quantity), but the encodedValues also includes an address owner field. In this case we need to pack in additional data to send from the calling contract to the policy. Also note the ForeignCalls defined in the policy, which are used in the rule evaluation as well as an effect for the positive mint outcome path.

Add the Rules Engine and SDK to your project

Before getting started, add these dependencies to your project.
npm i @fortefoundation/forte-rules-engine
npm i @fortefoundation/forte-rules-engine-sdk

Generate and Add Modifiers

To add the modifiers to your ClaireNFT.sol contract, create a new file named injectModifiers.ts and add the content below.
You will need to have the SDK installed in your project. Check the installation guide for details.
injectModifiers.ts
import { policyModifierGeneration } from "@fortefoundation/forte-rules-engine-sdk";

const modifiersPath = "src/RulesEngineClientCustom.sol";
const yourContract = "src/ClaireNFT.sol";

policyModifierGeneration("policy.json", modifiersPath, [yourContract]);
Run this script within your project directory to prepare your contract for deployment.
npx tsx injectModifiers.ts
This will create two new files in your project, src/RulesEngineClientCustom.sol and diff.diff. It will also update the existing ClaireNFT.sol file by adding the modifier to the mint function.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

contract ClaireNFT is ERC721, Ownable {
    uint256 private _nextTokenId = 1;

    constructor() ERC721("Claire NFT", "CLR") Ownable(msg.sender) {}

    function mint(address to, uint256 quantity) public {
        for (uint256 i = 0; i < quantity; i++) {
            _safeMint(to, _nextTokenId);
            _nextTokenId++;
        }
    }
}
The diff.diff file will show exactly what was changed in the ClaireNFT.sol file for your reference.
Because it was specified in the policy.json file, the modifier automatically added the owner argument to the modifier functions for you. And that’s it! The ClaireNFT.sol contract is now ready to use for this policy.

Passing Additional Values to the Rules Engine

In the example above the rule condition requires the address of the contract owner to bypass the rule condition. The owner value is not a function argument and must be explicitly passed to the modifier attached to the mint function. Refer to the code examples in the tabs above to see exactly how to do this. You can include any extra state variables in your smart contract that you want to leverage in your rule condition using this method.