import { createPublicClient, http, toHex } from "viem";
import { baseSepolia } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
import {
createBundlerClient,
entryPoint07Address,
UserOperation,
} from "viem/account-abstraction";
import { createPimlicoClient } from "permissionless/clients/pimlico";
import { toSafeSmartAccount } from "permissionless/accounts";
import axios from "axios";
const executeUserOperation = async () => {
try {
const owner = privateKeyToAccount("<PRIVATE_KEY>");
const publicClient = createPublicClient({
chain: baseSepolia,
transport: http("https://sepolia.base.org"),
});
const paymasterClient = createPimlicoClient({
transport: http("<PIMLICO_RPC_URL>"),
entryPoint: {
address: entryPoint07Address,
version: "0.7",
},
});
const safeAccount = await toSafeSmartAccount({
client: publicClient,
entryPoint: {
address: entryPoint07Address,
version: "0.7",
},
owners: [owner],
version: "1.4.1",
});
const bundlerClient = createBundlerClient({
account: safeAccount,
client: publicClient,
paymaster: paymasterClient,
transport: http("<PIMLICO_RPC_URL>"),
});
// Bridging ETH from Base Sepolia to ETH on Sepolia
// userOperationGasOverhead set to 300000
const requestData = {
user: safeAccount!.address,
originChainId: 84532,
destinationChainId: 11155111,
originCurrency: "0x0000000000000000000000000000000000000000",
destinationCurrency: "0x0000000000000000000000000000000000000000",
recipient: owner.address,
tradeType: "EXACT_INPUT",
amount: "20000000000000000",
referrer: "relay.link/swap",
useExternalLiquidity: false,
userOperationGasOverhead: 300000,
};
const quoteResponse = await axios.post(
"https://api.relay.link/quote",
requestData,
{
headers: {
"Content-Type": "application/json",
},
},
);
// Fetch the transaction data from the response of the quote call
const { steps } = quoteResponse.data;
const item = steps[0].items[0];
const { to, data, value } = item.data;
const callData = await safeAccount.encodeCalls([
{
to: to as `0x${string}`,
data,
value,
},
]);
const { callGasLimit, verificationGasLimit, preVerificationGas } =
await bundlerClient.estimateUserOperationGas({
account: safeAccount,
calls: [
{
to,
value,
data,
},
],
});
const userOperation = {
sender: safeAccount.address,
nonce: await safeAccount.getNonce(),
callData,
callGasLimit,
maxFeePerGas: 0n,
maxPriorityFeePerGas: 0n,
preVerificationGas,
verificationGasLimit,
paymasterPostOpGasLimit: 100_000n, // can be estimated specific to a paymaster
paymasterVerificationGasLimit: 200_000n, // can be estimated specific to a paymaster
signature: await safeAccount.getStubSignature(),
};
const { paymaster, paymasterData } =
await paymasterClient.getPaymasterData({
...userOperation,
chainId: baseSepolia.id,
entryPointAddress: entryPoint07Address,
});
(userOperation as UserOperation).paymaster = paymaster;
(userOperation as UserOperation).paymasterData = paymasterData;
userOperation.signature =
await safeAccount.signUserOperation(userOperation);
// Convert all values to hex
const hexUserOperation = Object.fromEntries(
Object.entries(userOperation).map(([key, value]) => {
if (typeof value === "number" || typeof value === "bigint") {
return [key, toHex(value)];
}
return [key, value];
}),
);
const eth_sendUserOperationResponse = await axios.post(
"https://api.relay.link/execute/user-op/84532",
{
jsonrpc: "2.0",
id: 1,
method: "eth_sendUserOperation",
params: [hexUserOperation, entryPoint07Address],
},
{
headers: {
"Content-Type": "application/json",
},
},
);
const { result } = eth_sendUserOperationResponse.data;
const transactionHash = await bundlerClient.waitForUserOperationReceipt({
hash: result,
});
console.log("transactionHash", transactionHash);
} catch (error) {
console.error("Error:", error);
}
};
executeUserOperation();