Skip to main content

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

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.

AfterTimestampEnforcer.sol
// 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
);
Important

Depending on the use case, it may be necessary to add additional caveats to restrict the delegation further.