Documentation Index
Fetch the complete documentation index at: https://docs.dynamic.xyz/docs/llms.txt
Use this file to discover all available pages before exploring further.
See full example here which uses Pimlico services for bundling, paymaster, gas estimation, etc.
Basic Implementation
Create app with the React Quickstart
Follow the React Quickstart Custom setup path: Ethereum (EVM) with Wagmi and viem. Use the quickstart’s Vite scaffold, or scaffold React with your own tooling and install the same packages. In the Dynamic dashboard, enable Ethereum under Chains & Networks and add your dev origin under Security → Allowed Origins. Install Delegation Toolkit
After the app is created, install the Delegation Toolkit:npm install @metamask/delegation-toolkit
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 };
}
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 };
}
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