Tutorial 1 — Send your first gasless transaction
In this tutorial, you will submit your first fully-gasless transaction from a smart account.
You will set up the necessary permissionless.js clients, create a user operation, ask Pimlico's verifying paymaster to sponsor it, and then submit it on-chain with Pimlico's bundler.
Steps
Get a Pimlico API key
To get started, please go to our dashboard and generate a Pimlico API key.
Clone the Pimlico tutorial template repository
We have created a Pimlico tutorial template repository that you can use to get started. It comes set up with Typescript, viem, and permissionless.js.
git clone https://github.com/pimlicolabs/tutorial-template.git pimlico-tutorial-1
cd pimlico-tutorial-1
Now, let's install the dependencies:
npm install
The main file we will be working with is index.ts
. Let's run it to make sure everything is working:
npm start
If everything has been set up correctly, you should see Hello world!
printed to the console.
Create the public and paymaster clients, and generate a private key
The public client will be responsible for querying the blockchain, while the paymaster client will be responsible for interacting with Pimlico's verifying paymaster endpoint and requesting sponsorship.
Make sure to replace YOUR_PIMLICO_API_KEY
in the code below with your actual Pimlico API key.
Let's open up index.ts
, and add the following to the bottom:
const apiKey = "YOUR_PIMLICO_API_KEY"
const paymasterUrl = `https://api.pimlico.io/v2/sepolia/rpc?apikey=${apiKey}`
const privateKey =
(process.env.PRIVATE_KEY as Hex) ??
(() => {
const pk = generatePrivateKey()
writeFileSync(".env", `PRIVATE_KEY=${pk}`)
return pk
})()
export const publicClient = createPublicClient({
transport: http("https://rpc.ankr.com/eth_sepolia"),
})
export const paymasterClient = createPimlicoPaymasterClient({
transport: http(paymasterUrl),
entryPoint: ENTRYPOINT_ADDRESS_V07,
})
Create the SmartAccount
instance
For the purposes of this guide, we will be using Safe accounts. This account is an ERC-4337 wallet controlled by a single EOA signer.
To create the Safe account, we will use the signerToSafeSmartAccount
utility function from permissionless.js. We need to specify the Safe version we are using as well as the global ERC-4337 EntryPoint address. For the signer, we will be using the previously generated private key.
Add the following to the bottom of index.ts
:
const account = await signerToSafeSmartAccount(publicClient, {
signer: privateKeyToAccount(privateKey),
entryPoint: ENTRYPOINT_ADDRESS_V07, // global entrypoint
safeVersion: "1.4.1",
})
console.log(`Smart account address: https://sepolia.etherscan.io/address/${account.address}`)
Let's run this code with npm start
. You should see the smart account address printed to the console.
Smart account address: https://sepolia.etherscan.io/address/0x374b42bCFAcf85FDCaAB84774EA15ff36D42cdA7
Create the bundler and smart account clients
Now that we have a SmartAccount
instance, we need to create a SmartAccountClient
instance to be able to transact from it. SmartAccountClient
is an almost drop-in replacement for a viem WalletClient
, but it also includes some additional functionality for interacting with smart accounts.
We then specify the optional sponsorUserOperation
middleware function, calling the sponsorUserOperation
function from the paymaster client. This will make sure that the user operation is sponsored by Pimlico's verifying paymaster.
Finally, we specify the gasPrice
middleware function to fetch the gas price from the bundler that we will use to submit the user operation in the next step.
Add the following to the bottom of index.ts
:
const bundlerUrl = `https://api.pimlico.io/v2/sepolia/rpc?apikey=${apiKey}`
const bundlerClient = createPimlicoBundlerClient({
transport: http(bundlerUrl),
entryPoint: ENTRYPOINT_ADDRESS_V07,
})
const smartAccountClient = createSmartAccountClient({
account,
entryPoint: ENTRYPOINT_ADDRESS_V07,
chain: sepolia,
bundlerTransport: http(bundlerUrl, {
timeout: 30_000 // Wait 30 seconds for user operation to be included
}),
middleware: {
gasPrice: async () => {
return (await bundlerClient.getUserOperationGasPrice()).fast
},
sponsorUserOperation: paymasterClient.sponsorUserOperation,
},
})
Submit a transaction from the smart account
Finally, let's submit a transaction from the smart account. We will send a transaction to the 0xd8da6bf26964af9d7eed9e03e53415d37aa96045
(vitalik.eth) address with 0x1234
as example callData
.
Underneath the hood, the SmartAccountClient
will build a user operation, request Pimlico paymaster sponsorship, sign it with the smart account's private key, and then submit it to the bundler. The bundler will then query for receipts until it sees the user operation included on-chain.
Add the following to the bottom of index.ts
:
const txHash = await smartAccountClient.sendTransaction({
to: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
value: 0n,
data: "0x1234",
})
console.log(`User operation included: https://sepolia.etherscan.io/tx/${txHash}`)
Let's run this code again with npm start
. You should see the transaction hash bundling the user operation on-chain printed to the console.
User operation included: https://sepolia.etherscan.io/tx/0x7a2b61b4b7b6e9e66c459e3c9c24c7a292fc6c740533ce35dbf58710960cc0e5
You can now view the transaction on the Sepolia testnet explorer. By sending this user operation, you have:
- Deployed the counterfactual smart account contract
- Had this newly-deployed smart account verify the private key's signature
- Made Pimlico's verifying paymaster sponsor the user operation's gas fees
- Executed a simple transaction to
vitalik.eth
's address
All in a couple lines of code.
Congratulations, you are now a pioneer of Account Abstraction! 🎉
Please get in touch if you have any questions or if you'd like to share what you're building!
Combined code
If you want to see the complete code that combines all of the previous steps, we uploaded it to a separate repository. If you're looking to run it, remember to replace the API key with your own!