Syntax for Defining a Policy

Policies are submitted to the Rules Engine SDK in the form of a JSON string, with the following available properties:
  • Policy: a string representing the name of the policy (optional)
  • Description: a short description of the policy (optional)
  • PolicyType: either open or closed (any contract can subscribe to an open policy whereas closed policies must approve the calling contract) - required
  • CallingFunctions: an array of Calling Function JSON objects (defined below) - required
  • ForeignCalls: an array of Foreign Call JSON objects (defined below) - required
  • Trackers: an array of Tracker JSON objects (defined below) - required
  • MappedTrackers: an array of Mapped Tracker JSON objects (defined below) - required
  • Rules: an array of Rule JSON objects (defined below) - required

Validation Rules

  • All referenced calling functions in rules must exist in the CallingFunctions array
  • All referenced foreign calls must exist in the ForeignCalls array
  • All referenced trackers must exist in the Trackers or MappedTrackers arrays
  • All component names within each array must be unique
  • Foreign Call naming is only required to be unique if a single calling function is using multiple foreign calls
If you are not yet familiar with the concepts of Foreign Calls, Trackers, and Rules, check out the linked concept docs. Here is a starting example:
{
  "Policy": "Alice's Policy",
  "Description": "A short explanation of this policy",
  "PolicyType": "open",
  "CallingFunctions": [
    {
      "name": "transfer(address recipient, uint256 amount)",
      "functionSignature": "transfer(address recipient, uint256 amount)",
      "encodedValues": "address recipient, uint256 amount, uint256 receiverBalance"
    }
  ],
  "ForeignCalls": [
    {
      "name": "GetAccessLevel",
      "address": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e",
      "function": "accessLevel(address)",
      "returnType": "uint256",
      "valuesToPass": "recipient",
      "mappedTrackerKeyValues": "",
      "callingFunction": "transfer(address recipient, uint256 amount)"
    }
  ],
  "Trackers": [
    {
      "name": "largeTransactionCount",
      "type": "uint256",
      "initialValue": 0
    }
  ],
  "MappedTrackers": [
    {
      "name": "largeTransactionCountPerUser",
      "keyType": "address",
      "valueType": "uint256",
      "initialKeys": ["0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e"],
      "initialValues": [0]
    }
  ],
  "Rules": [
    {
      "Name": "Access level check",
      "Description": "Checks access level on transfer",
      "condition": "(receiverBalance + amount > 100) AND (FC:GetAccessLevel < 1)",
      "positiveEffects": [
        "revert(\"Recipient Access Level too low to increase balance above 100\")"
      ],
      "negativeEffects": [],
      "callingFunction": "transfer(address recipient, uint256 amount)"
    },
    {
      "Name": "Large transaction check",
      "Description": "Alerts and tracks large transactions",
      "condition": "amount > 10000",
      "positiveEffects": ["emit Whale alert", "TRU:largeTransactionCount += 1"],
      "negativeEffects": [],
      "callingFunction": "transfer(address recipient, uint256 amount)"
    }
  ]
}

Calling Functions

The functions that when called will invoke the Policy. You define these in the CallingFunctions top level property with the following details:
  • name: The name of the calling function (must be unique within policy, leading/trailing whitespace automatically trimmed)
  • functionSignature: The signature of the calling function, which includes argument types and names (must follow Solidity syntax, must exactly match the function signature in the target smart contract)
  • encodedValues: a list of values that will be encoded along with the call to the rules engine from the calling contract (must contain valid parameter type declarations using supported Solidity types only)

Supported Parameter Types

The following Solidity types are supported for encodedValues parameters:
  • Primitive Types: uint256, address, bool, bytes, string
  • Array Types: uint256[], address[], bool[], bytes[], string[]
  • Fixed-Size Types: bytes (standard Solidity byte values)

Parameter Name Usage and References

  • Parameter names defined in encodedValues serve as local identifiers within the rule engine
  • Local Scope: Names are used for referencing within the current policy only
  • Rule References: Parameters can be referenced directly by name in rule conditions (e.g., value > 500)
  • Foreign Call References: Parameters can be passed to foreign calls using their local names
  • Name Independence: Parameter names do not need to match the actual smart contract parameter names
  • Consistent Usage: Once defined, parameter names must be used consistently throughout the policy

Integration Requirements

  • Function Signature Matching: The functionSignature must exactly match the target contract’s function signature including parameter types and order
  • ABI Compatibility: The calling function definition must be ABI-compatible with the target smart contract function

Function Resolution

The SDK supports flexible calling function resolution for rules and foreign calls:
  • References must use the name field
  • Case-insensitive fallback matching is available if exact matches fail

Foreign Calls

The ForeignCalls property contains an array of Foreign Call JSON objects. These represent all of the Foreign Calls that will be available within the policy. Each object consists of the following properties:
  • name: The name of the foreign call (must be unique within policy, leading/trailing whitespace automatically trimmed)
  • function: Function signature with supported argument types (must follow Solidity function syntax with parentheses, supported parameter types only)
  • address: The address of the foreign contract being called (must be valid Ethereum address format, automatically converted to checksummed format)
  • returnType: The return type of the function (must be supported Solidity type)
  • valuesToPass: Values to pass as function arguments (comma-separated format expected, can reference calling function parameters by name, use literal values, or mix both)
  • mappedTrackerKeyValues: Key-value mappings for tracker integration (comma-separated format expected when used, can reference calling function parameters for dynamic key resolution)
  • callingFunction: Reference to the associated calling function (must reference existing calling function, supports flexible resolution)

Parameter Passing and Key Value Usage

Values to Pass Examples

  • Parameter Reference: “to, value” - passes the to and value parameters from the calling function
  • Mixed Parameters and Literals: “to, 1000, value” - passes to parameter, literal value 1000, and value parameter
  • Literal Values Only: “‘admin’, 100, true” - passes string ‘admin’, number 100, and boolean true
  • Single Parameter: “to” - passes only the to parameter
  • Function Signature Matching: Parameter count and types must match the foreign call function signature

Mapped Tracker Key Values Examples

  • Dynamic Key from Parameter: “to” - uses the to parameter value as the mapped tracker key
  • Multiple Parameters: “to, from” - when multiple mapped tracker operations are needed using different parameter values
  • Empty/Unused: "" - when the foreign call doesn’t interact with mapped trackers

Parameter Resolution Rules

  • Parameter Names: Must match exactly with names defined in the calling function’s encodedValues
  • Type Compatibility: Passed values must be compatible with the foreign call function’s expected parameter types
  • Order Sensitivity: Parameters must be provided in the same order as the foreign call function signature
  • Reference Validation: All parameter names must exist in the associated calling function

Function Reference Resolution

  • Case-insensitive fallback matching is available if exact matches fail
  • When a reference like "Transfer(address,uint256)" doesn’t exactly match the stored function name "transfer(address,uint256)", the system converts both to lowercase for comparison as a fallback option

Trackers

The Trackers property contains an array of Tracker JSON objects. These represent all of the Trackers that will be available within the policy. Each object consists of the following properties:
  • name: The name of the tracker (must be unique within policy, leading/trailing whitespace automatically trimmed)
  • type: The type of the tracker (must be exact match to supported types)
  • initialValue: The initial value of the tracker (for string types - leading/trailing whitespace automatically trimmed; for array types - each string element is trimmed)

Supported Tracker Types

  • uint256 - Unsigned 256-bit integer
  • address - Ethereum address
  • bool - Boolean value (“true” / “false”)
  • bytes - Bytes value (must use prefix “0x” e.g. “0x1234”)
  • string - String value
  • Array variants of the above types (uint256[], address[], bool[], bytes[], string[])

Type-Value Validation

  • For single value types: initialValue must be a string
  • For array types: initialValue must be an array of strings
  • Values must be compatible with the specified type
Check out the Tracker Guide for more info on implementing, usage, and testing of trackers.

Mapped Trackers

The MappedTrackers property contains an array of Mapped Tracker JSON objects. These represent all of the Mapped Trackers that will be available within the policy. Each object consists of the following properties:
  • name: The name of the tracker (must be unique within policy, leading/trailing whitespace automatically trimmed)
  • keyType: The key type of the tracker (must be exact match to supported key types)
  • valueType: The value type of the tracker (must be exact match to supported types)
  • initialKeys: The initial keys of the tracker (array of strings, no automatic trimming on individual keys, must be unique values)
  • initialValues: The initial values of the tracker (for string value types - leading/trailing whitespace automatically trimmed; for array value types - each string element within arrays is trimmed)

Supported Key Types

  • uint256 - Unsigned 256-bit integer
  • address - Ethereum address
  • string - String value
  • bool - Boolean value (“true” / “false”)
  • bytes - Bytes value (must use prefix “0x” e.g. “0x1234”)

Supported Value Types

  • Same as regular tracker types (including array variants)

Mapped Tracker Validation Rules

  • name must be unique within the policy’s MappedTrackers array
  • keyType must be a supported tracker key type
  • valueType must be a supported tracker type
  • initialKeys and initialValues arrays must have the same length
  • initialKeys must contain unique values (no duplicates)
  • Each value in initialValues must match the specified valueType
  • Each key in initialKeys must match the specified keyType

Type-Value Validation for Mapped Trackers

  • For single value types: each entry in initialValues must be a string
  • For array value types: each entry in initialValues must be an array of strings
  • All keys must be valid for the specified keyType
  • All values must be valid for the specified valueType
Check out the Tracker Guide for more info on implementing, usage, and testing of trackers.

Rules

Now’s the fun part! The Rules property contains an array of rule definition objects. These represent all of the rules that are configured for the policy. Each object consists of the following properties:
  • Name: The name of the rule (optional, defaults to empty string if not provided, no special character restrictions, leading/trailing whitespace automatically trimmed)
  • Description: A description of the rule (optional, defaults to empty string if not provided, no special character restrictions, leading/trailing whitespace automatically trimmed)
  • condition: a string representing a conditional expression for the rule which must evaluate to a boolean (true or false) result. Must contain valid logical operators only (AND, OR, NOT), proper parentheses grouping required, no automatic trimming
  • positiveEffects: an array of effect expressions to be executed if the condition evaluates to TRUE (array of strings, no character restrictions on individual effect strings)
  • negativeEffects: an array of effect expressions to be executed if the condition evaluates to FALSE (array of strings, no character restrictions on individual effect strings)
  • callingFunction: a string representation of the function signature this rule will be attached to, for example transfer(address recipient, uint256 amount) (must reference existing calling function name, no special character restrictions)
  • order: Optional ordering value for rule execution sequence (numeric values only, must be unique across all rules if used, enables deterministic rule execution sequence)

Condition

Conditional expressions must evaluate to a boolean result. A conditional expression consists of values combined using operators, for example (receiverBalance + amount < 100) AND (FC:GetAccessLevel > 1). Values available to use in conditional expressions:
  • Static values of the following types: uint256, address, bytes (0x format), string
  • Referenced values:
    • values encoded by the calling function. Referenced by the name listed in the encodedValues section for the rule.
    • Trackers: trackers are referenced in the condition statement by TR: followed by the name of the tracker (as defined in the tracker JSON object in the policy), for example TR:largeTransactionCount
    • MappedTrackers: mapped trackers are referenced in the condition statement by TR: followed by the name of the tracker (as defined in the tracker JSON object in the policy), followed by the key wrapped in parenthesis, for example TR:largeTransactionCount(sender)
    • Result of a Foreign Call: foreign calls are referenced in the condition statement by FC: followed by the name of the foreign call (as defined in the foreign call JSON object in the policy), for example FC:GetAccessLevel

Condition Syntax Requirements and Restrictions

Parentheses Requirements

  • Balanced Parentheses: All opening ( must have matching closing ) parentheses
  • Proper Nesting: Nested parentheses must be properly structured
  • Automatic Spacing: Parentheses are automatically padded with spaces during parsing
  • Group Validation: Each parenthetical group is validated independently for operator consistency

Reference Syntax

  • Foreign Calls: Must use FC: prefix followed by the foreign call name (e.g., FC:GetBalance)
  • Trackers: Must use TR: prefix followed by the tracker name (e.g., TR:UserCount)
  • Mapped Trackers: Must use TR: prefix with parentheses for the key (e.g., TR:UserBalances(userAddress))
  • Case Sensitive: All reference names are case-sensitive and must exactly match component names
  • No Spaces: Reference names cannot contain spaces or special characters except parentheses for mapped trackers

Logical Operator Rules

  • Single Operator Per Group: Each set of parentheses can only contain one instance of AND or OR (either all AND or all OR, not mixed)
  • Case Sensitive: Operators must be in ALL CAPS (AND, OR, NOT)
  • Spacing: Operators are automatically padded with spaces during validation
  • No Multiple Operators: Cannot have multiple AND or OR operators at the same level without proper grouping

String Literals

  • Must be enclosed in single quotes (") for string comparisons
  • Example: TR:UserRole == "admin"

Supported Operators

The SDK supports 18 different operators organized into four categories: Logical Operators:
  • AND: Logical AND operator for combining conditions
  • OR: Logical OR operator for alternative conditions
  • NOT: Logical NOT operator for negation (placement and usage restrictions apply)
Comparison Operators:
  • == (equals): Checks if two values are equal
  • != (not equals): Checks if two values are not equal
  • > (greater than): Checks if left value is greater than right value
  • < (less than): Checks if left value is less than right value
  • >= (greater than or equal): Checks if left value is greater than or equal to right value
  • <= (less than or equal): Checks if left value is less than or equal to right value
Arithmetic Operators:
  • + (addition): Arithmetic addition operator
  • - (subtraction): Arithmetic subtraction operator
  • * (multiplication): Arithmetic multiplication operator
  • / (division): Arithmetic division operator
Assignment Operators (for effects):
  • = (assignment): Sets a value
  • += (add and assign): Adds to existing value
  • -= (subtract and assign): Subtracts from existing value
  • *= (multiply and assign): Multiplies existing value
  • /= (divide and assign): Divides existing value
Grouping:
  • () - Parentheses for operation precedence and logical grouping
Order of operations is not assumed, it must be explicit. Any combination of more than 2 boolean expressions must be grouped using parentheses. For example:
  • Not Supported: 1 == 1 AND 2 == 2 OR 3 == 4
  • Supported: 1 == 1 AND (2 == 2 OR 3 == 4)

Effects (positiveEffects and negativeEffects)

Effects under the positiveEffects property are triggered if the rule Condition evaluates to TRUE. Effects under the negativeEffects property are triggered if the rule condition evaluates to false. Effects can be one of four types: revert, event, tracker update, or foreign call.

Revert Effects

A revert effect will revert the transaction when triggered. The syntax is: revert(“Revert message”), where the message is optional, so could also simply use revert. The message length limit is 32 bytes.

Event Effects

A event effect will emit an event when triggered. The syntax is: emit <Event message>, for example emit Whale alert!. This uses a generic event and logs the message.

Tracker Update Effects

A tracker update effect will update a tracker’s value when triggered. The syntax is TRU:<trackerName> <updateOperator> <expression>, for example TRU:largeTransactionCount += 1. The syntax for mapped tracker updates is TRU:<trackerName>(<key>) <updateOperator> <expression>, for example TRU:largeTransactionCount(sender) += 1. The name of the tracker is the name defined in the Trackers.name property. One of the following update operators must be used:
  • +=: add the value following the operator to the current value of the tracker (both tracker and value must be uint256 types)
  • -=: subtract the value following the operator from the current value of the tracker (both tracker and value must be uint256 types)
  • *=: multiply the value following the operator by the current value of the tracker (both tracker and value must be uint256 types)
  • /=: divide the tracker by the value following the operator (both tracker and value must be uint256 types)
  • =: assign the value of the tracker to the value after the operator (all types supported, uint256, address, bytes, string)
For the expression, this follows the same syntax as conditional expressions. Except, you will not use any relational or boolean operators (<, >, ==, AND, OR) since trackers cannot be booleans.
Special considerations should be taken when utilizing a tracker within an Open policy. Since the policy is open, any/all contracts may subscribe to the policy, therefore, update the trackers.

Foreign Call Effects

A foreign call effect will call a foreign contract when triggered. The syntax is the same as when using a foreign call to get a value for an expression, e.g. FC:UpdateVIPStatus.

Example Policies

Here are three more examples to help solidify the concepts.

Simple rule

Rule: Allow transfers if there is a minimum balance of 500 in the receiving wallet and don’t allow transfers if the amount being sent is more than 1000.
{
  "Policy": "Transfer Constraints",
  "PolicyType": "open",
  "CallingFunctions": [
    {
      "name": "transfer(address recipient, uint256 amount)",
      "functionSignature": "transfer(address recipient, uint256 amount)",
      "encodedValues": "address recipient, uint256 amount"
    }
  ],
  "ForeignCalls": [],
  "Trackers": [],
  "MappedTrackers": [],
  "Rules": [
    {
      "Name": "Transfer restrictions",
      "Description": "Limits transfer amounts and balances",
      "condition": "(receiverBalance + amount >= 500) AND (amount <= 1000)",
      "positiveEffects": [],
      "negativeEffects": ["revert(\"Transfer violates constraints\")"],
      "callingFunction": "transfer(address recipient, uint256 amount)"
    }
  ]
}

Rule using a Foreign Call

Rule: Block transfers over 50,000 unless the sender is a VIP.
{
  "Policy": "VIP Transfer Restriction",
  "PolicyType": "open",
  "CallingFunctions": [
    {
      "name": "transfer(address recipient, uint256 amount)",
      "functionSignature": "transfer(address recipient, uint256 amount)",
      "encodedValues": "address recipient, uint256 amount"
    }
  ],
  "ForeignCalls": [
    {
      "name": "isVIP",
      "address": "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5",
      "function": "isVIP(address)",
      "returnType": "uint256",
      "valuesToPass": "0"
    }
  ],
  "Trackers": [],
  "MappedTrackers": [],
  "Rules": [
    {
      "Name": "Transfer restrictions",
      "Description": "Limits transfer amounts and balances",
      "condition": "(amount > 50000) AND (FC:isVIP(recipient) == 0)",
      "positiveEffects": ["revert(\"Only VIP can transfer over 50K\")"],
      "negativeEffects": [],
      "callingFunction": "transfer(address recipient, uint256 amount)"
    }
  ]
}

Rule using a Tracker

Rule: Only allow 5 mints per day.
{
  "Policy": "Daily Mint Limit",
  "PolicyType": "open",
  "CallingFunctions": [
    {
      "name": "mint(uint256 amount)",
      "functionSignature": "mint(uint256 amount)",
      "encodedValues": "uint256 amount"
    }
  ],
  "ForeignCalls": [],
  "Trackers": [
    {
      "name": "dailyMintCount",
      "type": "uint256",
      "initialValue": 0
    }
  ],
  "MappedTrackers": [],
  "Rules": [
    {
      "Name": "Mint restrictions",
      "Description": "Limits mints",
      "condition": "TR:dailyMintCount < 5",
      "positiveEffects": ["TRU:dailyMintCount += 1"],
      "negativeEffects": ["revert(\"Daily mint limit reached\")"],
      "callingFunction": "mint(uint256 amount)"
    }
  ]
}

Rule using a Mapped Tracker

Rule: Only allow 5 mints per day per address.
{
  "Policy": "Daily Mint Limit Per Address",
  "PolicyType": "open",
  "CallingFunctions": [
    {
      "name": "mint(uint256 amount)",
      "functionSignature": "mint(uint256 amount)",
      "encodedValues": "uint256 amount"
    }
  ],
  "ForeignCalls": [],
  "Trackers": [],
  "MappedTrackers": [
    {
      "name": "dailyMintCount",
      "keyType": "address",
      "valueType": "uint256",
      "initialKeys": ["0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e"],
      "initialValues": [0]
    }
  ],
  "Rules": [
    {
      "Name": "Mint restrictions",
      "Description": "Limits mints",
      "condition": "TR:dailyMintCount(minter) < 5",
      "positiveEffects": ["TRU:dailyMintCount(minter) += 1"],
      "negativeEffects": ["revert(\"Daily mint limit reached\")"],
      "callingFunction": "mint(uint256 amount)"
    }
  ]
}