Skip to main content

Restrict a delegation

Caveat enforcers are used to apply specific rules and conditions to a delegation, ensuring that delegated executions are only performed under predefined circumstances.

A delegation has a property caveats, which is an array of CaveatStructs. A CaveatStruct is specified as follows:

export type CaveatStruct = {
enforcer: Hex; // The address of the caveat enforcer contract.
terms: Hex; // Data passed to the caveat enforcer, describing how the redemption should be validated.
args: Hex; // Data that may be specified by the redeemer when redeeming the delegation (only used in limited cases).
};

The CaveatBuilder interface offers an intuitive way to define the caveats property of a delegation. Use the CaveatBuilder to easily ensure that your delegations grant only the necessary authority.

Create the CaveatBuilder

To use the CaveatBuilder, call the createCaveatBuilder() function, passing an instance of DeleGatorEnvironment. The environment can be accessed from the MetaMaskSmartAccount, as in this example:

const environment = delegatorSmartAccount.environment;

const caveatBuilder = createCaveatBuilder(environment);
note

By default, the CaveatBuilder does not allow empty caveats. To allow the CaveatBuilder to build an empty caveats array, provide the following configuration:

const caveatBuilder = createCaveatBuilder(environment, { allowEmptyCaveats: true });

Add caveats to the builder

You can add caveats to the builder using the addCaveat method, specifying the caveat type and its parameters. You can chain multiple calls to addCaveat as in the following example:

const caveats = caveatBuilder
// allowedTargets accepts an array of addresses.
// This caveat restricts the caller to only use the delegation to interact with the specified address.
.addCaveat("allowedTargets", ["0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92"])
// allowedMethods accepts an array of methods.
// This caveat restricts the caller to only use the delegation to invoke the specified methods.
.addCaveat("allowedMethods", [
"approve(address,uint256)",
"transfer(address,uint256)"
])
// limitedCalls accepts a number.
// This caveat restricts the caller to only use the delegation one time.
.addCaveat("limitedCalls", 1)
.build();
Important

Be aware that delegations without caveats are entirely permissive. It is crucial to add appropriate caveats to restrict the delegated authority sufficiently. Failing to do so could result in unintended access or actions.

For convenience, you can also pass the CaveatBuilder directly to the various helper methods for creating a delegation. For example:

const caveats = caveatBuilder
// allowedTargets accepts an array of addresses.
.addCaveat("allowedTargets", ["0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92"])
// allowedMethods accepts an array of methods.
.addCaveat("allowedMethods", [
"approve(address,uint256)",
"transfer(address,uint256)"
])
// limitedCalls accepts a number.
.addCaveat("limitedCalls", 1);

const delegation = createRootDelegation(
delegate,
delegator,
caveats
);

Caveat types

The CaveatBuilder supports various caveat types, each serving a specific purpose. These caveat types correspond to the out-of-the-box caveat enforcers that the MetaMask Delegation Toolkit provides.

allowedCalldata

Limits the calldata that is executed.

You can use this caveat to enforce function parameters. We strongly recommend using this caveat to validate static types and not dynamic types. You can validate dynamic types through a series of AllowedCalldataEnforcer terms, but this is tedious and error-prone.

Parameters

  1. Index in the calldata byte array (including the 4-byte method selector) where the expected calldata starts
  2. Expected calldata as a hex string

Example

caveatBuilder.addCaveat("allowedCalldata",
4,
encodeAbiParameters([
{ type: "string" },
{ type: "uint256" }
], [
"Hello Gator",
12345n
])
);
note

This example uses Viem's encodeAbiParameters utility to encode the parameters as ABI-encoded hex strings.

allowedMethods

Limits what methods the delegate can call.

Parameters

  1. An array of methods as 4-byte hex strings, ABI function signatures, or ABIFunction objects

Example

caveatBuilder.addCaveat("allowedMethods", [
"0xa9059cbb",
"transfer(address,uint256)",
{
name: 'transfer',
type: 'function',
inputs: [
{ name: 'recipient', type: 'address' },
{ name: 'amount', type: 'uint256' }
],
outputs: [],
stateMutability: 'nonpayable',
}
]);
note

This example adds the transfer function to the allowed methods in three different ways - as the 4-byte function selector, the ABI function signature, and the ABIFunction object.

allowedTargets

Limits what addresses the delegate can call.

Parameters

  1. An array of addresses as hex strings

Example

caveatBuilder.addCaveat("allowedTargets", [
"0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92",
"0xB2880E3862f1024cAC05E66095148C0a9251718b"
]);

argsEqualityCheck

Ensures that the args provided when redeeming the delegation are equal to the terms specified on the caveat.

Parameters

  1. The expected args as a hex string

Example

caveatBuilder.addCaveat("argsEqualityCheck",
"0xf2bef872456302645b7c0bb59dcd96ffe6d4a844f311ebf95e7cf439c9393de2"
);

blockNumber

Specifies a range of blocks through which the delegation will be valid.

Parameters

  1. After threshold block number as a bigint
  2. Before threshold block number as a bigint

You can specify 0n to indicate that there is no limitation on a threshold.

Example

caveatBuilder.addCaveat("blocknumber",
19426587n,
0n
);

deployed

Ensures a contract is deployed, and if not, deploys the contract.

Parameters

  1. A contract address as a hex string
  2. A factory address as a hex string
  3. Bytecode as a hex string

Example

caveatBuilder.addCaveat("deployed",
"0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92",
"0x4fa079Afaa26A601FF458bC826FB498621f5E2e1",
"0x..." // The deploy bytecode for the contract at 0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92
);

erc20TransferAmount

Limits the transfer of ERC-20 tokens.

Parameters

  1. An ERC-20 contract address as a hex string
  2. Amount as a bigint

Example

caveatBuilder.addCaveat("erc20TransferAmount",
"0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92",
1_000_000n
);

erc20BalanceGte

Ensures the delegator's ERC-20 balance has increased by at least a specified amount after the execution has been performed, regardless of what the execution is.

Parameters

  1. An ERC-20 contract address as a hex string
  2. Amount as a bigint

Example

caveatBuilder.addCaveat("erc20BalanceGte",
"0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92",
1_000_000n
);

id

Specifies an ID for multiple delegations. Once one of them is redeemed, the other delegations with the same ID are revoked.

Parameters

  1. An ID as a number

Example

caveatBuilder.addCaveat("id",
123456
);

limitedCalls

Limits the number of times the delegate can perform executions on the delegator's behalf.

Parameters

  1. A count as a number

Example

caveatBuilder.addCaveat("limitedCalls",
1
);

nativeBalanceGte

Ensures that the recipient's native token balance has increased by at least the specified amount.

Parameters

  1. The recipient's address as a hex string
  2. The amount by which the balance must have increased as a bigint

Example

caveatBuilder.addCaveat("nativeBalanceGte",
"0x3fF528De37cd95b67845C1c55303e7685c72F319",
1_000_000n
);

nativeTokenPayment

Enforces payment in native token (for example, ETH) for the right to use the delegation. A permissions context allowing payment must be provided as the args when redeeming the delegation.

Parameters

  1. The recipient's address as a hex string
  2. The amount that must be paid as a bigint

Example

caveatBuilder.addCaveat("nativeTokenPayment",
"0x3fF528De37cd95b67845C1c55303e7685c72F319",
1_000_000n
);

nativeTokenTransferAmount

Enforces an allowance of native currency (for example, ETH).

Parameters

  1. The allowance as a bigint

Example

caveatBuilder.addCaveat("nativeTokenTransferAmount",
1_000_000n
);

nonce

Adds a nonce to a delegation, and revokes previous delegations by incrementing the current nonce by calling incrementNonce(address _delegationManager).

Parameters

  1. A nonce as a hex string

Example

caveatBuilder.addCaveat("nonce",
"0x1"
);

redeemer

Limits the addresses that can redeem the delegation. This caveat enforcer is designed for restricting smart contracts or EOAs lacking delegation support, and can be placed anywhere in the delegation chain to restrict the redeemer.

note

Delegator accounts with delegation functionalities can bypass these restrictions by delegating to other addresses. For example, Alice makes Bob the redeemer. This condition is enforced, but if Bob is a delegator he can create a separate delegation to Carol that allows her to redeem Alice's delegation through Bob.

Parameters

  1. An array of addresses as hex strings

Example

caveatBuilder.addCaveat("redeemer",
[
"0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da",
"0x6be97c23596ECed7170fdFb28e8dA1Ca5cdc54C5"
]
);

timestamp

Specifies a range of timestamps through which the delegation will be valid.

Parameters

  1. After threshold timestamp as a number
  2. Before threshold timestamp as a number

You can specify 0 to indicate that there is no limitation on a threshold.

Example

caveatBuilder.addCaveat("timestamp",
499165200,
1445412480
);

valueLte

Limits the value of native tokens that the delegate can spend.

Parameters

  1. A value as a bigint

Example

caveatBuilder.addCaveat("valueLte",
1_000_000_000_000_000_000n // 1 ETH in wei
);