Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save himanshugarg06/4d94bba59dde9fb6594ddd94f209d272 to your computer and use it in GitHub Desktop.
Save himanshugarg06/4d94bba59dde9fb6594ddd94f209d272 to your computer and use it in GitHub Desktop.
Mint transaction using Safe smart account, biconomy bundler and ERC20 paymaster
// Safe smart accounts with Biconomy Bundler and Paymaster SDK
import { ENTRYPOINT_ADDRESS_V06, UserOperation } from "permissionless";
import {
SafeSmartAccount,
signerToSafeSmartAccount,
} from "permissionless/accounts";
import {
Chain,
createClient,
createPublicClient,
encodeFunctionData,
Hex,
http,
parseAbi,
PrivateKeyAccount,
PublicClient,
Transport,
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { IHybridPaymaster, IPaymaster, PaymasterMode, PaymasterUserOperationDto, createBundler, createPaymaster } from "@biconomy/account";
const DUMMY_GAS_LIMIT = 1000000n;
// Define constants from environment variables
const RPC_URL = "";
const PAYMASTER_URL = "https://paymaster.biconomy.io/api/v1/137/xxxxx";
const BUNDLER_URL = "https://bundler.biconomy.io/api/v2/137/xxxxxxxx";
const PRIVATE_KEY = "xxxxxxxx";
const nftAddress = "0x1758f42Af7026fBbB559Dc60EcE0De3ef81f665e";
// Create and initialize public, paymaster, and bundler clients
const createClients = async () => {
const publicClient = createPublicClient({
transport: http(RPC_URL),
});
const paymaster: IHybridPaymaster<PaymasterUserOperationDto> = await createPaymaster({
paymasterUrl: PAYMASTER_URL, // <-- Read about at https://docs.biconomy.io/dashboard/paymaster
strictMode: true
});
const bundlerClient = await createBundler({
bundlerUrl: BUNDLER_URL
});
return { publicClient, paymaster, bundlerClient };
};
// Retrieve the Safe smart account associated with the given signer
const getSafeAccount = async (
publicClient: PublicClient,
signer: PrivateKeyAccount,
) => {
return await signerToSafeSmartAccount(publicClient, {
entryPoint: ENTRYPOINT_ADDRESS_V06,
signer: signer,
safeVersion: "1.4.1",
});
};
// Check if the account's bytecode is deployed and get the initialization code if needed
const getInitCodeIfNeeded = async (
publicClient: PublicClient,
safeAccount: SafeSmartAccount<
"0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
Transport,
Chain | undefined
>,
) => {
const byteCode = await publicClient.getBytecode({
address: safeAccount.address,
});
return byteCode === undefined
? await safeAccount.getInitCode()
: ("0x" as `0x${string}`);
};
// Create the call data for minting the NFT
const getMintCallData = async (
safeAccount: SafeSmartAccount<
"0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
Transport,
Chain | undefined
>,
) => {
const parsedAbi = parseAbi(["function safeMint(address _to)"]);
const nftData = encodeFunctionData({
abi: parsedAbi,
functionName: "safeMint",
args: [safeAccount.address],
});
return await safeAccount.encodeCallData({
to: nftAddress,
value: 0n,
data: nftData,
});
};
// Main function to mint the NFT
export const mint = async () => {
try {
// Create necessary clients
const { publicClient, paymaster, bundlerClient } =
await createClients();
const signer = privateKeyToAccount(PRIVATE_KEY);
const safeAccount = await getSafeAccount(publicClient, signer);
console.log(safeAccount.address)
// Get the init code if needed
const initCode = await getInitCodeIfNeeded(publicClient, safeAccount);
const callData = await getMintCallData(safeAccount);
const nonce = await safeAccount.getNonce();
const gasFeeValues = await bundlerClient.getGasFeeValues()
const userOperation: any = {
sender: safeAccount.address,
nonce: Number(nonce).toString(),
initCode,
callData,
maxFeePerGas: gasFeeValues.maxFeePerGas,
maxPriorityFeePerGas: gasFeeValues.maxPriorityFeePerGas,
preVerificationGas: DUMMY_GAS_LIMIT,
callGasLimit: DUMMY_GAS_LIMIT,
verificationGasLimit: DUMMY_GAS_LIMIT,
}
const feeQuotesResponse = await paymaster.getPaymasterFeeQuotesOrData(
userOperation,
{
mode: PaymasterMode.ERC20,
tokenList: ["0x8f3cf7ad23cd3cadbd9735aff958023239c6a063"],
preferredToken: "0x8f3cf7ad23cd3cadbd9735aff958023239c6a063",
}
);
console.log(feeQuotesResponse)
let paymasterServiceData = {
mode: PaymasterMode.ERC20,
feeTokenAddress: "0x8f3cf7ad23cd3cadbd9735aff958023239c6a063",
calculateGasLimits: true, // Always recommended and especially when using token paymaster
};
const paymasterData = await paymaster.getPaymasterAndData(
userOperation,
paymasterServiceData
);
userOperation.paymasterAndData = paymasterData.paymasterAndData;
// const paymasterData = await paymaster.getPaymasterAndData(userOperation, {mode: PaymasterMode.SPONSORED})
userOperation.paymasterAndData = paymasterData.paymasterAndData;
userOperation.preVerificationGas = paymasterData.preVerificationGas;
userOperation.verificationGasLimit = paymasterData.verificationGasLimit;
userOperation.callGasLimit = paymasterData.callGasLimit;
userOperation.signature = "0x";
const dummySignature = await safeAccount.getDummySignature(userOperation);
userOperation.signature = dummySignature;
const signature = await safeAccount.signUserOperation(userOperation)
userOperation.signature = signature;
console.log(userOperation)
try{
const res = await bundlerClient.sendUserOp(userOperation)
console.log(res)
} catch(error) {
console.log("error occured", error)
}
} catch (error) {
console.error(`Error in minting NFT: ${error}`);
}
};
// Execute the mint function
mint();
//https://polygonscan.com/tx/0x672d9f6683e0b4e5db8890f6d6b126bf7b086a9f58ece3aa2fdedb2a91b91d27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment