Manage delegations
Delegation is the ability for an account owner (the delegator) to grant permission to another smart contract account (SCA) or externally owned account (EOA) to perform specific executions on the delegator's behalf, under defined rules and conditions. This page provides instructions to complete specific tasks within the delegation lifecycle.
Prerequisites
- Install and set up the MetaMask Delegation Toolkit.
- Configure the toolkit.
Create a delegation
A delegation is an instance of DelegationStruct
, where delegator
is the account granting permission to the delegate
account. Caveats are constraints placed on the granted permission.
export type DelegationStruct = {
delegate: Hex;
delegator: Hex;
authority: Hex;
caveats: CaveatStruct[];
salt: bigint;
signature: Hex;
};
The following is an example of creating a delegation using the provided helper function:
import {
createRootDelegation,
} from "@codefi/delegator-core-viem";
...
const delegation = createRootDelegation(
delegatorClient.account.address,
'0x2FcB88EC2359fA635566E66415D31dD381CF5585',
[]
);
This code sample assumes the existence of a delegatorClient
.
Restrict a delegation
In the previous example, no caveats enforcers were provided, which would grant the delegate complete control over the delegator's account. While this level of access might be acceptable in certain scenarios, such as delegating to another account you own or to a trusted family member, it is essentially equivalent to handing over your private keys.
We strongly recommend applying caveats enforcers to limit delegated permissions.
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 also create a custom caveat enforcer.
You can apply multiple caveats to a single delegation by adding the address of each caveat enforcer to the caveats
array in the delegation struct.
We recommend using the CaveatBuilder
interface to easily apply multiple caveat enforcers to a delegation. Learn more in Restrict a delegation.
Stacking caveats in this way enables highly customizable access control. For example, using a combination of out-of-the-box caveat enforcers can result in a delegation with the caveats that the delegate can:
- Only send up to 100 USDC.
- Only send it to Alice.
- Only send funds twice.
Open delegations
In some cases, it might be optimal to create more open-ended delegations by not specifying a single delegate.
Open delegations can be redeemed by any account that meets the requirements set by the caveat enforcers.
To create an open delegation, set the delegate
to the special value address(0xa11)
.
Sign a delegation
The delegator must sign a delegation in order for it to be valid. The following is an example of signing a delegation:
const signedDelegation = await delegatorClient.signDelegation(delegation);
This code sample assumes the existence of a delegatorClient
.
The user interaction this triggers depends on the configured signer.
Store a delegation
Delegations often need to be stored for extended periods of time, so it's crucial to store them securely until they are redeemed.
The MetaMask Delegation Toolkit offers a DelegationStorageClient
to provide storage for signed delegations.
Import the DelegationStorageClient
from the @codefi/delegator-core-viem
package. When using this service, delegations are stored in MetaMask's secure storage to ensure they are available when needed.
See more information about storing and retrieving delegations using DelegationStorageClient
.
Redeem a delegation
Redeeming a delegation occurs when the delegate initiates an on-chain execution within the bounds of the caveats. For example, Bob (the delegate) might transfer an NFT from Alice's account (the delegator) to Carol's account.
If there are no caveats limiting it, delegations can be redeemed multiple times.
To redeem a delegation:
- The delegation must first be signed by the delegator.
- The redemption is executed by sending a user operation that performs the delegated execution.
The redemption flow allows users to perform a single or multiple redemptions. Each redemption consists of three components:
- Permission context: An array representing the delegation chain, including all permissions and caveats required for execution. The caveats must be tailored to either single or batch executions.
- Executions: A list of execution instructions. Provide a single item for one execution or multiple items for batch execution.
- Execution mode: Specifies how to handle execution and errors.
The available modes are:
SINGLE_DEFAULT_MODE
: Performs a single execution; any failure reverts the entire operation.SINGLE_TRY_MODE
: Performs a single execution; failures don't revert the operation but trigger an event.BATCH_DEFAULT_MODE
: Performs multiple executions; any failure reverts the entire operation.BATCH_TRY_MODE
: Performs multiple executions; failures don't revert the operation but trigger an event.
The following are examples of redeeming a delegation.
The code samples assume the existence of a valid delegation to redeem.
Example: Single redemption
This example demonstrates how to redeem a delegation for a single execution:
// Define a single execution
const singleExecution: ExecutionStruct = {
target: account.address, // Target contract or account to interact with
callData: "0x", // Encoded function call data (empty in this example)
value: 0n, // Amount of native tokens to send (if applicable)
};
// Set the execution mode (e.g., SINGLE_DEFAULT_MODE for single execution with reversion on failure)
const executionMode: ExecutionMode = SINGLE_DEFAULT_MODE;
// Create a redemption object for the single execution
const singleRedemption: Redemption = {
permissionContext: [delegation], // Signed delegation with permissions and caveats
executions: [singleExecution], // Array containing the single execution
mode: executionMode, // Mode controlling error handling (single execution)
};
// Create and sign the user operation for the single redemption
const signedSingleUserOp = await client.createAndSignRedeemDelegationsUserOp(
[singleRedemption], // Array of redemptions (in this case, just one)
userOpOptions, // Additional user operation options (e.g., gas settings)
nonce // Transaction nonce to ensure unique execution
);
// Send the signed user operation to the bundler for on-chain execution
const { result: userOpHash } = await bundler.sendUserOp(
signedSingleUserOp, // The signed user operation
client.environment.EntryPoint // EntryPoint contract address handling the operation
);
// Log the result (user operation hash)
console.log(`User operation hash: ${userOpHash}`);
Example: Single redemption with batch execution
This example demonstrates how to redeem a delegation for multiple executions (batch):
// Define multiple executions for batch processing
const batchExecutions: ExecutionStruct[] = [
{
target: contract1.address, // First target contract or account
callData: "0x1234", // Encoded function call data for the first execution
value: 0n, // Amount of native tokens to send (if applicable)
},
{
target: contract2.address, // Second target contract or account
callData: "0x5678", // Encoded function call data for the second execution
value: 0n, // Amount of native tokens to send (if applicable)
}
];
// Set the execution mode for batch processing
const executionMode: ExecutionMode = BATCH_DEFAULT_MODE; // Reverts on failure
// Create a redemption object for batch execution
const batchRedemption: Redemption = {
permissionContext: [delegation], // Signed delegation for the batch
executions: batchExecutions, // Array of executions for batch processing
mode: executionMode, // Batch execution mode controlling error handling
};
// Create and sign the user operation for batch redemption
const signedBatchUserOp = await client.createAndSignRedeemDelegationsUserOp(
[batchRedemption], // Array of redemptions (can be multiple redemptions in one batch)
userOpOptions,
nonce
);
// Send the signed user operation for batch execution
const { result: userOpHash } = await bundler.sendUserOp(
signedBatchUserOp,
client.environment.EntryPoint
);
// Log the result (user operation hash)
console.log(`User operation hash: ${userOpHash}`);
Example: Batch of redemptions
This example demonstrates how to send multiple redemptions in a single user operation:
// Create multiple redemption objects (e.g., for multiple delegations)
const redemption1: Redemption = {
permissionContext: [delegation1],
executions: [execution1],
mode: SINGLE_DEFAULT_MODE,
};
const redemption2: Redemption = {
permissionContext: [delegation2],
executions: [execution2],
mode: SINGLE_TRY_MODE,
};
// Create and sign the User Operation for multiple redemptions
const signedBatchOfRedemptionsUserOp = await client.createAndSignRedeemDelegationsUserOp(
[redemption1, redemption2], // Multiple redemptions in one user operation
userOpOptions,
nonce
);
// Send the signed User Operation for multiple redemptions
const { result: userOpHash } = await bundler.sendUserOp(
signedBatchOfRedemptionsUserOp,
client.environment.EntryPoint
);
// Log the result (User Operation hash)
console.log(`User operation hash: ${userOpHash}`);
Redelegate a delegation
Redelegating is the act of assigning an existing delegation to a new account. When redelegating:
- The account redelegating retains the same permissions as before they redelegated, and any caveats applied to their delegation are unchanged.
- All caveats applied to the delegation are maintained and enforced on the account that receives the redelegation.
The following is an example of redelegating a delegation:
import {
createDelegation,
getDelegationHashOffchain,
} from "@codefi/delegator-core-viem";
const delegate = ACCOUNT_RECEIVING_REDELEGATION;
const delegator = ACCOUNT_WITH_EXISTING_DELEGATION;
const authority = getDelegationHashOffchain(delegation);
createDelegation(delegate, delegator, authority, []);