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

Basic Implementation

1

Create app with the React Native Quickstart

Follow the React Native Quickstart Custom setup path and enable Ethereum (EVM) with the extensions your stack needs (for example Viem). In the Dynamic dashboard, enable Ethereum under Chains & Networks and add your dev origin under SecurityAllowed Origins.
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