Skip to main content
The Time Range Policy restricts when your agent can execute actions. Essential for scheduled automation, temporary access, and safety limits.

When to Use Time Range

Subscription payments (monthly windows)
Trial periods for new users
Scheduled automation (run only at specific times)
Emergency time-boxing for new agents
Combining with Sudo for safety

Two Levels of Time Bounds

Session-Level (Global)

Applies to all actions in the session:
const sessionDetails = await sessionClient.grantPermissionTypedDataSign({
  // These apply to EVERYTHING
  sessionValidAfter: Math.floor(Date.now() / 1000),           // Start now
  sessionValidUntil: Math.floor(Date.now() / 1000) + 30 * DAY, // End in 30 days
  
  actions: [...]
});

Action-Level (Per-Function)

Different time windows for different actions:
actions: [
  {
    chainId: base.id,
    actionTarget: CONTRACT_A,
    actionTargetSelector: selectorA,
    // This action only valid for 7 days
    validAfter: now,
    validUntil: now + 7 * DAY,
    actionPolicies: [getSudoPolicy()]
  },
  {
    chainId: base.id,
    actionTarget: CONTRACT_B,
    actionTargetSelector: selectorB,
    // This action valid for full 30 days
    validAfter: now,
    validUntil: now + 30 * DAY,
    actionPolicies: [getSudoPolicy()]
  }
]

Agent Examples

Subscription Agent (Monthly Windows)

Only allow payments on the 1st of each month:
const getMonthlyWindow = () => {
  const now = new Date();
  const firstOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
  const secondOfMonth = new Date(now.getFullYear(), now.getMonth(), 2);
  
  return {
    validAfter: Math.floor(firstOfMonth.getTime() / 1000),
    validUntil: Math.floor(secondOfMonth.getTime() / 1000)
  };
};

const sessionDetails = await sessionClient.grantPermissionTypedDataSign({
  redeemer: agentSigner.address,
  feeToken: { address: USDC, chainId: base.id },
  
  // 1 year session
  sessionValidUntil: Math.floor(Date.now() / 1000) + 365 * 24 * 60 * 60,
  
  actions: [{
    chainId: base.id,
    actionTarget: USDC,
    actionTargetSelector: toFunctionSelector("transfer(address,uint256)"),
    ...getMonthlyWindow(),  // Only valid on 1st of month
    usageLimit: 12n,
    actionPolicies: [paymentPolicy]
  }]
});

Trial Agent (7-Day Trial)

Limited access for evaluation:
const SEVEN_DAYS = 7 * 24 * 60 * 60;

const sessionDetails = await sessionClient.grantPermissionTypedDataSign({
  redeemer: agentSigner.address,
  feeToken: { address: USDC, chainId: base.id },
  
  sessionValidAfter: Math.floor(Date.now() / 1000),
  sessionValidUntil: Math.floor(Date.now() / 1000) + SEVEN_DAYS,
  
  // Limited capabilities during trial
  usageLimit: 10n,  // Only 10 actions
  maxPaymentAmount: parseUnits("5", 6),  // $5 gas max
  
  actions: [/* trial actions */]
});

Trading Agent (Market Hours Only)

Only trade during specific hours:
// Note: Time validation happens at execution time
// For complex schedules, implement in agent logic

const sessionDetails = await sessionClient.grantPermissionTypedDataSign({
  redeemer: agentSigner.address,
  feeToken: { address: USDC, chainId: base.id },
  
  // 30-day access
  sessionValidUntil: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60,
  
  actions: [{
    chainId: base.id,
    actionTarget: UNISWAP,
    actionTargetSelector: swapSelector,
    actionPolicies: [tradingPolicy]
  }]
});

// In your agent logic:
function shouldTrade() {
  const hour = new Date().getUTCHours();
  // Only trade during US market hours (14:30 - 21:00 UTC)
  return hour >= 14 && hour < 21;
}

Yield Optimizer (Quarterly Rebalancing)

Restrict to specific rebalancing windows:
const getQuarterlyWindow = () => {
  const now = new Date();
  const quarter = Math.floor(now.getMonth() / 3);
  const quarterStart = new Date(now.getFullYear(), quarter * 3, 1);
  const windowEnd = new Date(quarterStart);
  windowEnd.setDate(7);  // First week of quarter
  
  return {
    validAfter: Math.floor(quarterStart.getTime() / 1000),
    validUntil: Math.floor(windowEnd.getTime() / 1000)
  };
};

const sessionDetails = await sessionClient.grantPermissionTypedDataSign({
  redeemer: agentSigner.address,
  feeToken: { address: USDC, chainId: base.id },
  
  // Full year session
  sessionValidUntil: Math.floor(Date.now() / 1000) + 365 * 24 * 60 * 60,
  
  actions: [{
    chainId: base.id,
    actionTarget: REBALANCER,
    actionTargetSelector: rebalanceSelector,
    ...getQuarterlyWindow(),
    usageLimit: 4n,  // 4 quarters
    actionPolicies: [getSudoPolicy()]
  }]
});

Delayed Start

Start permissions in the future:
const ONE_WEEK = 7 * 24 * 60 * 60;

const sessionDetails = await sessionClient.grantPermissionTypedDataSign({
  // Starts in 1 week
  sessionValidAfter: Math.floor(Date.now() / 1000) + ONE_WEEK,
  sessionValidUntil: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60,
  
  actions: [...]
});
Use cases:
  • Scheduled launches
  • Vesting schedules
  • Delayed automation

Combining with Other Policies

Time Range works best combined with other restrictions:

Time + Sudo (Safe Sudo)

const sessionDetails = await sessionClient.grantPermissionTypedDataSign({
  sessionValidUntil: now + 7 * DAY,  // Only 7 days
  
  actions: [{
    actionTarget: TRUSTED_CONTRACT,
    actionPolicies: [getSudoPolicy()]  // Full access, but time-limited
  }]
});

Time + Usage Limit

const sessionDetails = await sessionClient.grantPermissionTypedDataSign({
  sessionValidUntil: now + 30 * DAY,
  
  actions: [{
    usageLimit: 100n,  // Max 100 actions in 30 days
    actionPolicies: [getSudoPolicy()]
  }]
});

Time + Universal Action

const sessionDetails = await sessionClient.grantPermissionTypedDataSign({
  sessionValidUntil: now + 7 * DAY,
  
  actions: [{
    actionPolicies: [
      getUniversalActionPolicy({ /* spending limits */ })
    ]
  }]
});

Time Format

All timestamps are Unix timestamps in seconds (not milliseconds):
// ✅ Correct - seconds
const validUntil = Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60;

// ❌ Wrong - milliseconds
const validUntil = Date.now() + 7 * 24 * 60 * 60 * 1000;

Helper Functions

const DAY = 24 * 60 * 60;
const WEEK = 7 * DAY;
const MONTH = 30 * DAY;
const YEAR = 365 * DAY;

const now = () => Math.floor(Date.now() / 1000);
const inDays = (days: number) => now() + days * DAY;
const inWeeks = (weeks: number) => now() + weeks * WEEK;
const inMonths = (months: number) => now() + months * MONTH;

// Usage
sessionValidUntil: inDays(7),
sessionValidUntil: inWeeks(2),
sessionValidUntil: inMonths(1),

Common Mistakes

Using milliseconds instead of seconds
// ❌ This expires immediately (timestamp in year 50000+)
sessionValidUntil: Date.now() + 7 * 24 * 60 * 60 * 1000

// ✅ Correct
sessionValidUntil: Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60
Action time window exceeds session windowAction-level bounds must be within session-level bounds:
// ❌ Action valid longer than session
sessionValidUntil: now + 7 * DAY,
actions: [{
  validUntil: now + 30 * DAY  // This won't work past day 7
}]