Create a custom caveat enforcer
Caveat enforcers are used to apply specific rules and conditions to a delegation, ensuring that delegated executions are only performed under predefined circumstances.
The MetaMask Delegation Toolkit provides some out-of-the-box caveat enforcers that cover common use cases. For more granular or custom control, you can create custom caveat enforcers from scratch.
Prerequisites
- Install and set up the MetaMask Delegation Toolkit.
- Configure the toolkit.
Steps
1. Create the caveat enforcer
Create a contract that extends the
ICaveatEnforcer.sol
interface.
For example, the following is a simple caveat enforcer that only allows a delegation to be redeemed after a specific timestamp.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
import { CaveatEnforcer } from "@delegator/src/enforcers/CaveatEnforcer.sol";
import { ModeCode } from "../utils/Types.sol";
contract AfterTimestampEnforcer is CaveatEnforcer {
/**
* @notice The delegation may only be redeemed after the specified timestamp - validAfter in seconds.
* @param _terms The validAfter, timestamp in seconds after which the delegation may be redeemed.
* @param _delegationHash - The hash of the delegation being operated on.
*/
function beforeHook(
bytes calldata _terms,
bytes calldata,
ModeCode,
bytes calldata,
bytes32 _delegationHash,
address,
address _redeemer
)
public
override
{
// Enforces the conditions that should hold before a transaction is performed.
// This function MUST revert if the conditions are not met.
// Get the current timestamp
uint256 timestamp = block.timestamp;
uint256 validAfter = uint256(bytes32(_terms));
require(timestamp > validAfter, "AfterTimestampEnforcer:cannot-redeem-too-early");
}
}
2. Deploy the caveat enforcer
Deploy your custom caveat enforcer to obtain its contract address. For example, you can deploy your smart contract using Forge.
3. Apply the caveat enforcer
When creating a delegation, add the CaveatStruct
for the custom caveat to the CaveatBuilder
.
Learn more about applying caveats to a delegation.
The following example uses the AfterTimestampEnforcer.sol
caveat enforcer to create a delegation granting
an allowance of 1,000,000 wei that can only be spent after one hour from when the delegation is created.
import {
createCaveatBuilder,
createRootDelegation,
getDelegatorEnvironment
} from "@metamask-private/delegator-core-viem";
import { toHex } from "viem";
import { sepolia } from "viem/chains";
const environment = createDelegatorEnvironment(sepolia.id)
// Replace this with the address where the AfterTimestampEnforcer.sol contract is deployed.
const afterTimestampEnforcer = "0x22Ae4c4919C3aB4B5FC309713Bf707569B74876F";
const caveatBuilder = createCaveatBuilder(environment);
const tenAM = 10 * 60 * 60; // 10:00 AM as seconds since midnight.
const caveats = caveatBuilder
.addCaveat("nativeTokenTransferAmount", 1_000_000)
.addCaveat({
enforcer: afterTimestampEnforcer,
terms: toHex(tenAm)
});
const delegation = createRootDelegation(
delegate,
delegator,
caveats
);
Depending on the use case, it may be necessary to add additional caveats to restrict the delegation further.