How to use Metamask delegation toolkit with permissionless.js | Pimlico Docs
Skip to content

How to use Metamask delegation toolkit with permissionless.js

Metamask Delegation Toolkit is a smart account that supports delegation of the smart account to another signer with granular permission sharing. It is build over ERC-7710 and ERC-7715 to support a standardized minimal interface. Requesting ERC-7715 permissions and redeeming ERC-7710 delegations are experimental features.

You can have two types of account in Metamask Delegation Toolkit:

  1. Delegator Account: A smart account that support programmable account behavior and advanced features such as multi-signature approvals, automated transaction batching, and custom security policies.
  2. Delegate Account: An account (smart account or EOA) that receives the delegation from the delegator account to perform actions on behalf of the delegator account.

You can use both accounts with permissionless.js.

Installation

We will be using Metamask's official SDK to create a smart account.

npm
npm install permissionless viem @metamask/delegation-toolkit

Delegator Account

Create the clients

First we must create the public, (optionally) pimlico paymaster clients that will be used to interact with the Metamask account.

export const publicClient = createPublicClient({
	transport: http("https://sepolia.rpc.thirdweb.com"),
});
 
export const paymasterClient = createPimlicoClient({
	transport: http("https://api.pimlico.io/v2/sepolia/rpc?apikey=API_KEY"),
	entryPoint: {
		address: entryPoint07Address,
		version: "0.7",
	},
});

Create the signer

Metamask accounts can work with a variety of signing algorithms such as ECDSA, passkeys, and multisig.

For example, to create a signer based on a private key:

import { privateKeyToAccount } from "viem/accounts";
import { createPimlicoClient } from "permissionless/clients/pimlico";
import { entryPoint07Address } from "viem/account-abstraction";
 
const owner = privateKeyToAccount("0xPRIVATE_KEY");

Create the Metamask delegator account

With a signer, you can create a Metamask account as such:

import {
	Implementation,
	toMetaMaskSmartAccount,
} from "@metamask/delegation-toolkit";
 
const delegatorSmartAccount = await toMetaMaskSmartAccount({
	client: publicClient,
	implementation: Implementation.Hybrid,
	deployParams: [owner.address, [], [], []],
	deploySalt: "0x",
	signatory: { account: owner },
});
 

Create the smart account client

const smartAccountClient = createSmartAccountClient({
	account: delegatorSmartAccount,
	chain: sepolia,
	paymaster: paymasterClient,
	bundlerTransport: http(
		"https://api.pimlico.io/v2/sepolia/rpc?apikey=API_KEY",
	),
	userOperation: {
		estimateFeesPerGas: async () =>
			(await paymasterClient.getUserOperationGasPrice()).fast,
	},
});

Send a transaction

Transactions using permissionless.js simply wrap around user operations. This means you can switch to permissionless.js from your existing viem EOA codebase with minimal-to-no changes.

const txHash = await smartAccountClient.sendTransaction({
	to: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
	value: parseEther("0.1"),
});

This also means you can also use viem Contract instances to transact without any modifications.

const nftContract = getContract({
	address: "0xFC3e86566895Fb007c6A0d3809eb2827DF94F751",
	abi: tokenAbi,
	client: {
		public: publicClient,
		wallet: smartAccountClient,
	},
});
 
const txHash = await nftContract.write.mint([
	"0x_MY_ADDRESS_TO_MINT_TOKENS",
	parseEther("1"),
]);

You can also send an array of transactions in a single batch.

const userOpHash = await smartAccountClient.sendUserOperation({
	calls: [
		{
			to: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
			value: parseEther("0.1"),
			data: "0x",
		},
		{
			to: "0x1440ec793aE50fA046B95bFeCa5aF475b6003f9e",
			value: parseEther("0.1"),
			data: "0x1234",
		},
	],
});

Delegate Account

A delegate account is an account that receives the delegation from the delegator account to perform actions on behalf of the delegator account. To create a delegate account, we will follow the following steps:

  1. Create a delegate signer
  2. Create the delegate smart account
  3. Create a delegation using delegator smart account
  4. Sign the delegation
  5. Send transactions using delegate smart account with signed delegation

Create the clients

First we must create the public, (optionally) pimlico paymaster clients that will be used to interact with the Metamask account.

export const publicClient = createPublicClient({
	transport: http("https://sepolia.rpc.thirdweb.com"),
});
 
export const paymasterClient = createPimlicoClient({
	transport: http("https://api.pimlico.io/v2/sepolia/rpc?apikey=API_KEY"),
	entryPoint: {
		address: entryPoint07Address,
		version: "0.7",
	},
});

Create the signer

Metamask accounts can work with a variety of signing algorithms such as ECDSA, passkeys, and multisig.

For example, to create a signer based on a private key:

const delegateSigner = privateKeyToAccount("0xPRIVATE_KEY");

Create the Metamask delegate account

With a delegate signer, you can create a Metamask delegate account as such:

const delegateSmartAccount = await toMetaMaskSmartAccount({
	client: publicClient,
	implementation: Implementation.Hybrid,
	deployParams: [delegateSigner.address, [], [], []],
	deploySalt: "0x",
	signatory: { account: delegateSigner },
});

Create a delegation

This example passes an empty caveats array, which means the delegate can perform any action on the delegator's behalf. We recommend restricting the delegation by adding caveat enforcers.

import { createDelegation } from "@metamask/delegation-toolkit";
 
const delegation = createDelegation({
	to: delegateSmartAccount.address,
	from: delegatorSmartAccount.address,
	caveats: [],
});

Sign the delegation

const signature = await delegatorSmartAccount.signDelegation({
	delegation,
});
 
const signedDelegation = {
	...delegation,
	signature,
};

Create the smart account client

const delegateSmartAccountClient = createSmartAccountClient({
	account: delegateSmartAccount,
	chain: sepolia,
	paymaster: paymasterClient,
	bundlerTransport: http(
		"https://api.pimlico.io/v2/sepolia/rpc?apikey=API_KEY",
	),
	userOperation: {
		estimateFeesPerGas: async () =>
			(await paymasterClient.getUserOperationGasPrice()).fast,
	},
});

Send transactions using signed delegation

import {
	DelegationFramework,
	SINGLE_DEFAULT_MODE,
} from "@metamask/delegation-toolkit";
 
const delegations = [signedDelegation];
 
// Actual execution to be performed by the delegate account
const executions = [
	{
		target: "0xFC3e86566895Fb007c6A0d3809eb2827DF94F751" as Hex,
		value: parseEther("0"),
		callData: encodeFunctionData({
			abi: tokenAbi,
			functionName: "mint",
			args: ["0x_MY_ADDRESS_TO_MINT_TOKENS", parseEther("1")],
		}),
	},
];
 
const redeemDelegationCalldata = DelegationFramework.encode.redeemDelegations({
	delegations: [delegations],
	modes: [SINGLE_DEFAULT_MODE],
	executions: [executions],
});
 
const txHash = await delegateSmartAccountClient.sendTransaction({
	calls: [
		{
			to: delegateSmartAccount.address,
			data: redeemDelegationCalldata,
		},
	],
});