This guide provides a detailed comparison between two approaches for implementing light account smart wallets with different bundlers/paymasters services, focusing on the minimal code changes required to switch between implementations.
- SDK: Permissionless vs Gelato Smart Wallet SDK (
@gelatonetwork/smartwallet
) - Bundlers/Paymasters: Pimlico vs Gelato Relay
- Account Type: Light Account
In this repo you will be able to:
Before running this demo, make sure you have:
-
Clone and Install Dependencies
git clone https://github.com/gelatodigital/lightaccount-pimlico-to-gelatosdk-bundler-migration.git cd lightaccount-pimlico-to-gelatosdk-bundler-migration npm install
-
Set Up Environment Variables
cp .env.example .env
Then edit
.env
and add your API keys:NEXT_PUBLIC_GELATO_API_KEY=your_gelato_api_key_here
-
Start the Development Server
npm run dev
-
Open Your Browser Navigate to http://localhost:3000 to see the demo in action.
For sponsored transactions (gasless), you'll need a Sponsor API Key:
- Visit the Gelato Relay App
- Create an account and generate a Sponsor API Key for Arbitrum Sepolia
- Add the key to your
.env
file
Need help? Check out our How to Create a Sponsor API Key Guide
const pimlicoSmartAccountClient = createSmartAccountClient({
chain: arbitrum,
account: smartAccountClient?.account,
bundlerTransport: http(`/api/pimlico?chainId=${arbitrum.id}`),
userOperation: {
estimateFeesPerGas: async () => {
return {
maxFeePerGas: BigInt(0),
maxPriorityFeePerGas: BigInt(0),
};
},
},
pollingInterval: 500,
});
Imports
import { gelatoBundlerActions } from "@gelatonetwork/smartwallet/adapter";
import { sponsored, WalletEncoding } from "@gelatonetwork/smartwallet";
import { useWalletClient } from "wagmi";
Code
const { data: walletClient } = useWalletClient();
const smartAccount = await toLightSmartAccount({
client: publicClient as any,
owner: walletClient as any,
entryPoint: {
address: entryPoint07Address,
version: "0.7",
},
version: "2.0.0",
});
const gelatoSmartAccountClient = createSmartAccountClient({
account: smartAccount,
chain: arbitrumSepolia,
// Important: Chain transport (chain rpc) must be passed here instead of bundler transport
bundlerTransport: http(),
}).extend(
gelatoBundlerActions({
payment: sponsored(process.env.NEXT_PUBLIC_SPONSOR_API_KEY as string),
encoding: WalletEncoding.LightAccount,
})
);
Migration Change: Remove Pimlico’s client config and replace it with Gelato’s Smart Account Client Adapter.
const hash = await pimlicoSmartAccountClient.sendTransaction({
calls: [
{
to: "0x0000000000000000000000000000000000000000" as `0x${string}`,
value: BigInt(0),
data: "0x",
},
],
});
const userOpHash = await gelatoSmartAccountClient.sendUserOperation({
calls: [
{
to: zeroAddress,
data: "0x",
value: BigInt(0),
},
],
});
Migration Change:
- Replace
sendTransaction
withsendUserOperation
method
// BEFORE: Light Account + Pimlico
import { useCallback } from "react";
import { createSmartAccountClient } from "permissionless";
import { useSmartWallets } from "@privy-io/react-auth/smart-wallets";
import { arbitrum } from "viem/chains";
import { http } from "viem";
interface ICalls {
to: string;
data: string;
}
export const useSendSmartWalletOrder = () => {
const { client: smartAccountClient } = useSmartWallets();
const prepareAndSendUserOperation = useCallback(
async (calls: ICalls[]) => {
const pimlicoSmartAccountClient = createSmartAccountClient({
chain: arbitrum,
account: smartAccountClient?.account,
bundlerTransport: http(`/api/pimlico?chainId=${arbitrum.id}`),
userOperation: {
estimateFeesPerGas: async () => {
return {
maxFeePerGas: BigInt(0),
maxPriorityFeePerGas: BigInt(0),
};
},
},
pollingInterval: 500,
});
const signedTransaction =
await pimlicoSmartAccountClient?.sendTransaction({
calls: calls,
});
return {
signedTransaction,
};
},
[smartAccountClient?.account]
);
const sendSmartWalletOrder = useCallback(
async (calls: ICalls[]) => {
try {
const { signedTransaction } = await prepareAndSendUserOperation(calls);
return { hash: signedTransaction };
} catch (err: any) {
console.log("err", err);
}
},
[prepareAndSendUserOperation]
);
return { sendSmartWalletOrder };
};
// AFTER: Gelato Smart Wallet SDK + Light Account
import { useCallback } from "react";
import { createSmartAccountClient } from "permissionless";
import { arbitrum } from "viem/chains";
import { useWalletClient } from "wagmi";
import { http } from "viem";
interface ICalls {
to: string;
data: string;
}
export const useSendSmartWalletOrder = () => {
const { data: walletClient } = useWalletClient();
const prepareAndSendUserOperation = useCallback(
async (calls: ICalls[]) => {
const smartAccount = await toLightSmartAccount({
client: publicClient as any,
owner: walletClient as any,
entryPoint: {
address: entryPoint07Address,
version: "0.7",
},
version: "2.0.0",
});
const gelatoSmartAccountClient = createSmartAccountClient({
account: smartAccount,
chain: arbitrumSepolia,
// Important: Chain transport (chain rpc) must be passed here instead of bundler transport
bundlerTransport: http(),
}).extend(
gelatoBundlerActions({
payment: sponsored(process.env.NEXT_PUBLIC_SPONSOR_API_KEY as string),
encoding: WalletEncoding.LightAccount,
})
);
const userOpHash = await gelatoSmartAccountClient?.sendUserOperation({
calls: calls,
});
const receipt =
await gelatoSmartAccountClient.waitForUserOperationReceipt({
hash: userOpHash,
});
const transactionHash = receipt.receipt.transactionHash;
return {
transactionHash,
};
},
[smartAccountClient?.account]
);
const sendSmartWalletOrder = useCallback(
async (calls: ICalls[]) => {
try {
const { transactionHash } = await prepareAndSendUserOperation(calls);
return { hash: transactionHash };
} catch (err: any) {
console.log("err", err);
}
},
[prepareAndSendUserOperation]
);
return { sendSmartWalletOrder };
};
The main differences between the two approaches are:
-
Account Creation: The current implementation uses useSmartWallets() from Pyth, which creates an EntryPoint v0.6 account. However, this is not compatible with Gelato Smart Wallets, which require EntryPoint v0.7.
So You must explicitly create a LightAccount using EntryPoint v0.7 to ensure compatibility with the Gelato Smart Wallet system.
-
Client Creation: When setting up the smart account client, extend it with
gelatoBundlerActions
to enable Gelato-specific functionalities. -
Transaction Flow:
- Pimlico: Uses
sendTransaction()
method - Gelato: Uses
sendUserOperation()
method
- Pimlico: Uses
-
Paymaster Integration:
- Pimlico: Explicit paymaster client configuration
- Gelato: Built-in sponsored payment through bundler actions