Skip to main content
See full example here which uses Pimlico services for bundling, paymaster, gas estimation, etc.

Basic Implementation

1

Create Dynamic app with Viem & Wagmi

npx create-dynamic-app my-mm-smart-account-app --framework react --library viem --wagmi true --chains ethereum --pm npm
2

Install Delegation Toolkit

After the app is created, install the Delegation Toolkit:
npm install @metamask/delegation-toolkit
3

Create Smart Account Hook

src/hooks/useSmartAccount.ts
import {
  Implementation,
  MetaMaskSmartAccount,
  toMetaMaskSmartAccount,
} from "@metamask/delegation-toolkit";
import { useEffect, useState } from "react";
import { useAccount, usePublicClient, useWalletClient } from "wagmi";

export default function useSmartAccount(): {
  smartAccount: MetaMaskSmartAccount | null;
} {
  const { address } = useAccount();
  const publicClient = usePublicClient();
  const { data: walletClient } = useWalletClient();
  const [smartAccount, setSmartAccount] = useState<MetaMaskSmartAccount | null>(
    null
  );

  useEffect(() => {
    if (!address || !walletClient || !publicClient) return;

    console.log("Creating smart account");

    toMetaMaskSmartAccount({
      client: publicClient,
      implementation: Implementation.Hybrid,
      deployParams: [address, [], [], []],
      deploySalt: "0x",
      signatory: { walletClient },
    }).then((smartAccount) => {
      setSmartAccount(smartAccount);
    });
  }, [address, walletClient, publicClient]);

  return { smartAccount };
}
4

Create Bundler Client Hook

src/hooks/useBundlerClient.ts
import { createBundlerClient } from "viem/account-abstraction";
import { useState, useEffect } from "react";
import { usePublicClient } from "wagmi";
import { http } from "viem";

export function useBundlerClient() {
    const [bundlerClient, setBundlerClient] = useState();
    const publicClient = usePublicClient();

    useEffect(() => {
        if (!publicClient) return;
        setBundlerClient(createBundlerClient({
            client: publicClient,
            transport: http("https://your-bundler-rpc.com"),
        }));
    }, [publicClient]);

    return { bundlerClient };
}
5

Send User Operation

src/components/SendUserOperation.tsx
import { parseEther } from "viem";
import useBundlerClient from "/hooks/useBundlerClient";
import useSmartAccount from "../hooks/useSmartAccount";

const SendUserOperation = () => {
    const { bundlerClient } = useBundlerClient();
    const { smartAccount } = useSmartAccount();

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

    const handleSendUserOperation = async () => {
        const userOperationHash = await bundlerClient.sendUserOperation({
            account: smartAccount,
            calls: [
                {
                    to: "0x1234567890123456789012345678901234567890",
                    value: parseEther("1"),
                },
            ],
            maxFeePerGas,
            maxPriorityFeePerGas,
        });
        // You may want to handle the result here, e.g., show a notification
        console.log("User Operation Hash:", userOperationHash);
    };

    return (
        <button onClick={handleSendUserOperation}>
            Send User Operation
        </button>
    );
}

References

I