Skip to main content

Create a social invite link

delegation toolkitsocialinvitereferrallinkMetaMask Developer Relations | Sep 8, 2025

This tutorial walks you through creating an invite link to enable users to refer their friends to your dapp with minimal friction.

For example, Alice (the inviter) wants Bob (the invitee) to try out your dapp. She sends him a link that allows him to claim 0.001 ETH from her wallet within a time limit. Bob can start using your dapp right away, without installing a wallet or paying gas fees.

You'll enable this by creating a MetaMask smart account to represent the inviter, using a paymaster to abstract gas fees, and creating an open delegation to represent an invitation. This tutorial uses Pimlico's paymaster, but you can use any paymaster of your choice.

Prerequisites

Steps

1. Install the Delegation Toolkit

Install the MetaMask Delegation Toolkit in your project:

npm install @metamask/delegation-toolkit

2. Create a Public Client

Create a Viem Public Client using Viem's createPublicClient function. You will configure a smart account and Bundler Client with the Public Client, which you can use to query the signer's account state and interact with the blockchain network.

import { createPublicClient, http } from 'viem';
import { sepolia as chain } from 'viem/chains';

const publicClient = createPublicClient({
chain,
transport: http(),
});

3. Create a Paymaster Client

Create a Viem Paymaster Client using Viem's createPaymasterClient function. This client interacts with the paymaster service.

Replace <YOUR-API-KEY> with your Pimlico API key:

import { createPaymasterClient } from 'viem/account-abstraction';

const paymasterClient = createPaymasterClient({
transport: http('https://api.pimlico.io/v2/11155111/rpc?apikey=<YOUR-API-KEY>'),
});

4. Create a Bundler Client

Create a Viem Bundler Client using Viem's createBundlerClient function. Pass the paymasterClient to the paymaster property. You can use the bundler service to estimate gas for user operations and submit transactions to the network.

import { createBundlerClient } from 'viem/account-abstraction';

const bundlerClient = createBundlerClient({
client: publicClient,
transport: http('https://your-bundler-rpc.com'),
paymaster: paymasterClient,
});

5. Create and deploy an inviter account

Create an account to represent the inviter. This account will be creating a delegation, and must be a MetaMask smart account. This example uses a Hybrid smart account, which is a flexible smart account implementation that supports both an externally owned account (EOA) owner and any number of passkey (WebAuthn) signers.

import { Implementation, toMetaMaskSmartAccount } from '@metamask/delegation-toolkit';
import { privateKeyToAccount } from 'viem/accounts';

const account = privateKeyToAccount('0x...');

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

Deploy the smart account by sending a user operation:

import { zeroAddress } from 'viem';

// Appropriate fee per gas must be determined for the specific bundler being used.
const maxFeePerGas = 1n;
const maxPriorityFeePerGas = 1n;

const userOperationHash = await bundlerClient.sendUserOperation({
account: smartAccount,
calls: [{ to: zeroAddress }],
maxFeePerGas,
maxPriorityFeePerGas,
});

Fund the deployed smart account with some Sepolia ETH to sponsor gas fees for the invitee.

note

You can use the MetaMask faucet to get Sepolia ETH.

6. Create and sign an invitation

Create an open root delegation to represent an invitiation. A root delegation is the first delegation in a chain of delegations, and an open root delegation grants permission to any account. In this example, the inviter creates an invitation that can be redeemed by any invitee, allowing the invitee to spend up to 0.001 ETH.

import { createOpenDelegation, getDelegatorEnvironment } from '@metamask/delegation-toolkit';

const delegation = createOpenDelegation({
from: smartAccount.address,
environment: getDelegatorEnvironment(chain.id);
scope: {
type: 'nativeTokenTransferAmount',
// 0.001 ETH in wei format.
maxAmount: 1000000000000000n,
},
});

Sign the delegation to enable the invitee to redeem it in the future:

const signature = await smartAccount.signDelegation({
delegation,
})

const signedDelegation = {
...delegation,
signature,
}

7. Create an invitee account

Create an account to represent the invitee. This account will be redeeming the delegation and can be a smart account or an externally owned account (EOA). This example uses an EOA:

import { privateKeyToAccount } from 'viem/accounts';
import { sepolia as chain } from 'viem/chains';
import { createWalletClient, http } from 'viem';

const delegateAccount = privateKeyToAccount('0x...');

export const delegateWalletClient = createWalletClient({
account: delegateAccount,
chain,
transport: http(),
})

8. Redeem the invitation

The invitee can redeem the invitation by submitting a transaction to the DelegationManager contract, which validates the delegation and executes delegated actions. In this case, the invitee can spend up to 0.001 ETH from the inviter when using your dapp.

import { createExecution, getDeleGatorEnvironment } from '@metamask/delegation-toolkit'
import { DelegationManager } from '@metamask/delegation-toolkit/contracts'
import { SINGLE_DEFAULT_MODE } from '@metamask/delegation-toolkit/utils'
import { zeroAddress } from 'viem'

const delegations = [signedDelegation]

const executions = createExecution({ target: zeroAddress })

const redeemDelegationCalldata = DelegationManager.encode.redeemDelegations({
delegations: [delegations],
modes: [SINGLE_DEFAULT_MODE],
executions: [executions]
});

const transactionHash = await delegateWalletClient.sendTransaction({
to: getDeleGatorEnvironment(chain.id).DelegationManager,
data: redeemDelegationCalldata,
chain,
})

Next steps