Created
August 22, 2024 06:47
-
-
Save himanshugarg06/4d94bba59dde9fb6594ddd94f209d272 to your computer and use it in GitHub Desktop.
Mint transaction using Safe smart account, biconomy bundler and ERC20 paymaster
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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