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
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
| Condition | Meaning | Use Case |
|---|
equal | Must equal ref | Whitelist addresses |
lessThan | Must be < ref | Amount caps |
lessThanOrEqual | Must be ≤ ref | Amount caps (inclusive) |
greaterThan | Must be > ref | Minimum amounts |
greaterThanOrEqual | Must be ≥ ref | Minimum amounts |
notEqual | Must not equal ref | Blacklist |
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
- Check offsets: Use
console.log(encodeFunctionData(...)) to see actual calldata
- Match decimals: USDC = 6, ETH = 18, etc.
- Test on testnet: Verify rules work before mainnet