A Safe module that enables scheduling transactions with time-based execution windows using EIP-712 signatures.
⚠️ WARNING: This contract has NOT been audited. Use at your own risk.
The ScheduledTxModule allows Safe owners to pre-sign transactions that can only be executed within a specific time window. This is useful for:
- Delayed execution: Schedule transactions to execute at a future time
- Time-bounded permissions: Grant temporary execution rights that expire after a deadline
- Automation: Enable third parties to execute pre-authorized transactions within defined time constraints
- Sign: Safe owner(s) create an EIP-712 signature authorizing a transaction with time constraints
- Wait: Transaction cannot execute until
executeAftertimestamp - Execute: Anyone can call
execute()with the signature betweenexecuteAfteranddeadline - Expire: Transaction becomes invalid after
deadline
function execute(
address safe,
address to,
uint256 value,
bytes memory data,
uint256 nonce,
uint64 executeAfter,
uint64 deadline,
bytes memory signatures
) externalParameters:
safe: Address of the Safe contractto: Destination address for the transactionvalue: ETH amount to send (in wei)data: Transaction calldatanonce: Unique transaction identifier (prevents replay)executeAfter: Earliest execution timestamp (Unix timestamp)deadline: Latest execution timestamp (Unix timestamp)signatures: EIP-712 signature(s) from Safe owner(s)
Reverts:
TooEarly: Whenblock.timestamp < executeAfterTransactionExpired: Whenblock.timestamp > deadlineAlreadyExecuted: When nonce has been usedTransactionCancelled: When the nonce has been cancelled by the SafeModuleTxFailed: When Safe transaction execution fails
function cancel(uint256 nonce) externalCancels a scheduled transaction so it can never be executed. Must be called by the Safe itself. Any execute() for that Safe and nonce will then revert with TransactionCancelled. Cancellation does not require the corresponding permit to have been signed on-chain.
Parameters:
nonce: The nonce of the scheduled transaction to cancel
Reverts:
AlreadyExecuted: When the nonce has already been executed for the caller
Emits: Cancelled(address indexed safe, uint256 indexed nonce)
ScheduledTxModule(
address to,
uint256 value,
bytes data,
uint256 nonce,
uint64 executeAfter,
uint64 deadline
)Domain Separator:
EIP712Domain(
string name, // "ScheduledTxModule"
string version, // "1"
uint256 chainId, // Current chain ID
address verifyingContract // Module address
)forge install 5afe/scheduled-tx-modulegit clone https://github.com/5afe/scheduled-tx-module.git
cd scheduled-tx-module
forge installFirst, enable the module on your Safe:
safe.enableModule(scheduledTxModuleAddress);const domain = {
name: "ScheduledTxModule",
version: "1",
chainId: chainId,
verifyingContract: moduleAddress
};
const types = {
ScheduledTxModule: [
{ name: "to", type: "address" },
{ name: "value", type: "uint256" },
{ name: "data", type: "bytes" },
{ name: "nonce", type: "uint256" },
{ name: "executeAfter", type: "uint64" },
{ name: "deadline", type: "uint64" }
]
};
const value = {
to: recipientAddress,
value: ethers.parseEther("1.0"),
data: "0x",
nonce: 0,
executeAfter: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
deadline: Math.floor(Date.now() / 1000) + 86400 // 24 hours from now
};
const signature = await signer.signTypedData(domain, types, value);scheduledTxModule.execute(
safeAddress,
recipientAddress,
1 ether,
"",
0,
executeAfter,
deadline,
signature
);- Signature Validity: Signatures remain valid even if Safe configuration changes (e.g., adding owners), as long as the original signer is still an owner
- Cancellation: A Safe can cancel a pending scheduled transaction by calling
cancel(uint256 nonce). (Removing the signer or disabling the module also prevents execution, but affects all pending transactions.) - Permissionless Execution: Anyone can execute a properly signed transaction within the time window
- Nonce Management: Nonces are per-Safe and non-sequential
forge buildforge testforge test -vvvforge fmtforge snapshotThe project includes following tests:
- Basic ETH transfers
- ERC20 token transfers
- Time window enforcement (too early/expired)
- Replay protection
- Invalid signature rejection
- Cross-Safe signature isolation
- Module disable handling
- Safe configuration changes
This project uses Foundry, a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.