Skip to main content
The Universal Action Policy provides granular control over function parameters. Use it when your agent handles funds and you need spending limits, recipient whitelisting, or parameter validation.

When to Use Universal Action

Agent handles user funds
Need per-action spending limits
Need cumulative spending caps
Want to restrict recipients
Need parameter validation

Core Concepts

Parameter Rules

Each rule validates one parameter in the function call and maximum of 16 rules can be added per policy.
const universalPolicy = mcNexus.buildActionPolicy({
  type: "universal",
  rules: [
    {
      condition: "equal", // Comparison type
      calldataOffset: calldataArgument(1), // Argument position
      comparisonValue: 10n || "0x..", // Value to compare against
      isLimited: true, // Track cumulative usage
      usage: { limit: parseUnits("5000", 6), used: 0n }  // Cumulative limit
    }
  ],
  // Configure spending limit for native token
  valueLimitPerUse: 1n
});

Conditions

ConditionMeaningUse Case
equalMust equal refWhitelist addresses
lessThanMust be < refAmount caps
lessThanOrEqualMust be ≤ refAmount caps (inclusive)
greaterThanMust be > refMinimum amounts
greaterThanOrEqualMust be ≥ refMinimum amounts
notEqualMust not equal refBlacklist

Trading Agent Example

Limit per-trade and max spending limit per session:
const UNISWAP_ROUTER = "0x2626664c2603336E57B271c5C0b26F421741e481";

const tradingPolicy = mcNexus.buildActionPolicy({
  type: "universal",
  rules: [
    // Rule 1: Recipient must be user's account
    {
      condition: "equal",
      calldataOffset: calldataArgument(4),
      comparisonValue: userAccountAddress,
    },
    // Rule 2: Max $500 per trade, $5000 cumulative
    {
      condition: "lessThanOrEqual",
      calldataOffset: calldataArgument(5),
      comparisonValue: parseUnits("500", 6),
      isLimited: true,  // Track cumulative
      usage: { 
        limit: parseUnits("5000", 6),  // $5000 total limit
        used: 0n 
      }
    }
  ],
  valueLimitPerUse: 0n,  // No ETH value
});

const now = Math.floor(Date.now() / 1000)
const validAfter = now
const validUntil = now + 7 * 24 * 60 * 60

const timeframePolicy = mcNexus.buildActionPolicy({
  type: "timeframe",
  validAfter, // unix-timestamp when policy becomes active
  validUntil  // unix-timestamp when policy expires
});

const UNISWAP_ROUTER = "0x2626664c2603336E57B271c5C0b26F421741e481";

const customActions = mcNexus.buildSessionAction({
  type: "custom",
  data: {
    chainIds: [8453, 10],
    contractAddress: UNISWAP_ROUTER,
    functionSignature: '0x...',
    policies: [tradingPolicy, timeframePolicy]
  }
});

// Finally get the session quote and execute the quote to enable permissions

Payment Agent Example

Fixed recipient and amount:
const MERCHANT = "0x1234...";
const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";

const paymentPolicy = mcNexus.buildActionPolicy({
  type: "universal",
  rules: [
    // Only send to merchant
    {
      condition: "equal",
      calldataOffset: calldataArgument(1),
      comparisonValue: MERCHANT
    },
    // Max 100 USDC per payment
    {
      condition: "lessThanOrEqual",
      calldataOffset: calldataArgument(2),
      comparisonValue: parseUnits("100", 6),
    }
  ],
  // Configure spending limit for native token
  valueLimitPerUse: 0n,
});

const usagePolicy = mcNexus.buildActionPolicy({
  type: "usageLimit",
  limit: 12n,  // 12 payments max
});

const now = Math.floor(Date.now() / 1000)
const validAfter = now
const validUntil = now + 365 * 24 * 60 * 60

const timeframePolicy = mcNexus.buildActionPolicy({
  type: "timeframe",
  validAfter, // unix-timestamp when policy becomes active
  validUntil  // unix-timestamp when policy expires
});

// Finally use these policies in the action and enable permission for agents.
const policies = [paymentPolicy, usageLimit, timeframePolicy];

Rebalancing Agent Example

Limit rebalance size to % of portfolio:
const rebalancePolicy = mcNexus.buildActionPolicy({
  type: "universal",
  rules: [
    // Max 10% of portfolio per rebalance ($10,000 cap)
    {
      condition: "lessThanOrEqual",
      calldataOffset: calldataArgument(4),
      comparisonValue: parseUnits("10000", 6), // $10k
      isLimited: true,
      usage: { 
        limit: parseUnits("50000", 6),  // $50k total per period
        used: 0n 
      }
    }
  ],
  valueLimitPerUse: 0n,
});

Per-Action vs Cumulative Limits

Per-Action Only

Each individual action must be ≤ limit, but no cumulative tracking:
const universalPolicy = mcNexus.buildActionPolicy({
  type: "universal",
  rules: [
    {
      condition: "lessThanOrEqual",
      calldataOffset: calldataArgument(2),
      comparisonValue: parseUnits("500", 6), // Max $500 per trade
      // No cumulative tracking
    }
  ]
});

Cumulative Tracking

Track total spent across all actions:
const universalPolicy = mcNexus.buildActionPolicy({
  type: "universal",
  rules: [
    {
      condition: "lessThanOrEqual",
      calldataOffset: calldataArgument(2),
      comparisonValue: parseUnits("500", 6), // Max $500 per trade
      isLimited: true,  // Track cumulative
      usage: { 
        limit: parseUnits("5000", 6),  // $5000 total
        used: 0n 
      }
    }
  ]
});

Multiple Rules Example

Validate multiple parameters:
// For transfer(address recipient, uint256 amount)
const universalPolicyOne = mcNexus.buildActionPolicy({
  type: "universal",
  rules: [
    // Rule 1: Recipient in whitelist (address A)
    {
      condition: "equal",
      calldataOffset: calldataArgument(1),
      comparisonValue: ALLOWED_RECIPIENT_A
    },
    // Rule 2: Amount limit
    {
      condition: "lessThanOrEqual",
      calldataOffset: calldataArgument(2),
      comparisonValue: parseUnits("1000", 6),
      isLimited: true,
      usage: { limit: parseUnits("10000", 6), used: 0n }
    }
  ]
});

const universalPolicyTwo = mcNexus.buildActionPolicy({
  type: "universal",
  rules: [
    // Rule 1: recipient B (rules are OR'd for same offset)
    {
      condition: "equal",
      calldataOffset: calldataArgument(1),
      comparisonValue: ALLOWED_RECIPIENT_B
    },
    // Rule 3: Amount limit
    {
      condition: "lessThanOrEqual",
      calldataOffset: calldataArgument(2),
      comparisonValue: parseUnits("1000", 6),
      isLimited: true,
      usage: { limit: parseUnits("10000", 6), used: 0n }
    }
  ]
});

// This allows the recipient to be either A or B
const policies = [universalPolicyOne, universalPolicyTwo];

Finding Parameter Positions:

For a function like swap(address tokenIn, address tokenOut, uint256 amount):
calldataArgument(1):  tokenIn (address)
calldataArgument(2):  tokenOut (address)  
calldataArgument(3):  amount (uint256)
For structs, calldataArguments are more complex. Check the ABI encoding.

Common Patterns

Whitelist Single Recipient

const universalPolicy = mcNexus.buildActionPolicy({
  type: "universal",
  rules: [
    {
      condition: "equal",
      calldataOffset: calldataArgument(1),
      comparisonValue: ALLOWED_ADDRESS
    },
  ]
});

Cap Per Transaction Amount

const universalPolicy = mcNexus.buildActionPolicy({
  type: "universal",
  rules: [
    {
      condition: "lessThanOrEqual",
      calldataOffset: calldataArgument(2),
      comparisonValue: parseUnits("500", 6)
    },
  ]
});

Total Max Spending Limit

const universalPolicy = mcNexus.buildActionPolicy({
  type: "universal",
  rules: [
    {
      condition: "lessThanOrEqual",
      calldataOffset: calldataArgument(2),
      comparisonValue: parseUnits("500", 6), // Per-tx max
      isLimited: true,
      usage: { 
        limit: parseUnits("5000", 6),  // Cumulative max
        used: 0n 
      }
    },
  ]
});

Minimum Amount

const universalPolicy = mcNexus.buildActionPolicy({
  type: "universal",
  rules: [
    {
      condition: "greaterThanOrEqual",
      calldataOffset: calldataArgument(2),
      comparisonValue: parseUnits("10", 6), // At least $10
    },
  ]
});

Debugging Tips

  1. Check offsets: Use console.log(encodeFunctionData(...)) to see actual calldata
  2. Match decimals: USDC = 6, ETH = 18, etc.
  3. Test on testnet: Verify rules work before mainnet