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 */ })
]
}]
});
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
}]