Account Abstraction API
Gasless Transactions

Gasless Transactions

In this tutorial, you will submit your first fully-gasless transaction from a smart account using the Owl Protocol ERC4337 (opens in a new tab) Account Abstraction API and permissionless (opens in a new tab).

You will set up the necessary permissionless clients, create a user operation (opens in a new tab) (aka ERC4337 transaction), ask the Owl Protocol verifying paymaster to sponsor it, and then submit it on-chain with the Owl Protocol bundler.

Clone The Repo

💡

The tutorials repository (opens in a new tab) contains a basic index.ts file to follow along tutorials as well as the final code of all Owl Protocol tutorials under the /tutorials folder.

We have created the Owl Protocol tutorials repository (opens in a new tab) to get you started quickly. It comes set up with Typescript, viem, permissionless, and Owl Protocol SDKs.

git clone https://github.com/owlprotocol/tutorials.git owlprotocol-tutorials
cd owlprotocol-tutorials

Now, let's install the dependencies (we recommend using pnpm (opens in a new tab))

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 something similar to this printed to the console.

Welcome to Owl Protocol!
API_KEY_SECRET not found! Ensure it's correctly set in your .env file.

To fix the error you will need an API Key.

Get Your API Key

⚠️

API Key

Never expose your API Key in the frontend or client-side code. Your API Key is crucial for authenticating your requests to Owl Protocol. Always keep it secure and use it only in server-side code to prevent unauthorized access and ensure the security of your project.

Go to owl.build (opens in a new tab) to get your API Key. If this is your first time signing up, you will automatically have a default team (My Team) and a default project (My Project).

Copy the API key to your clipboard and paste it into the .env file in the tutorial template repository. This should silence the error when you run pnpm start and only show "Welcome to Owl Protocol!".

projectApiKey

Create The Public, Paymaster, Bundler Clients

Clients provide access to sets of actions using the viem (opens in a new tab) library. For example, a public client provides access to readable data such as transactions and blocks.

To send gasless transactions using ERC4337 Account Abstraction, we will need 3 important clients:

Let's open up index.ts, and add the following to the bottom

/***** Create Clients *****/
// The id of the blockchain we wish to connect to, replace this with any
// chainId supported by Owl Protocol.
// Here we use the Hedwig Testnet for quick testing
const chain = {
    chainId: 150150,
    slug: "hedwig-testnet",
    name: "Hedwig Testnet",
    testnet: true,
    nativeCurrency: {
        decimals: 18,
        name: 'Ether',
        symbol: 'ETH',
    },
    rpcUrls: {
        default: {
            http: ["https://rpc-hedwig-testnet-6uuksiwu6t.t.conduit.xyz"],
            webSocket: ["wss://rpc-hedwig-testnet-6uuksiwu6t.t.conduit.xyz"],
        },
    },
};
const chainId = 150150;
const blockExplorer = "https://explorer-testnet.hedwig.build"
 
// Create public viem client to read data from blockchain
// Learn more at https://viem.sh/docs/clients/public
const publicClient = createPublicClient({
    transport: http(`https://api.owl.build/${chainId}/rpc?apikey=${API_KEY_SECRET}`),
})
 
// Create paymaster viem client to sponsor UserOp
// Learn more at https://docs.pimlico.io/permissionless/reference/clients/pimlicoPaymasterClient
const paymasterUrl = `https://api.owl.build/${chainId}/rpc?apikey=${API_KEY_SECRET}`
const paymasterClient = createPimlicoPaymasterClient({
    transport: http(paymasterUrl),
    entryPoint: ENTRYPOINT_ADDRESS_V07,
})
 
// Create bundler viem client to submit UserOp
// Learn more at https://docs.pimlico.io/permissionless/reference/clients/bundlerClient
const bundlerUrl = `https://api.owl.build/${chainId}/rpc?apikey=${API_KEY_SECRET}`
const bundlerClient = createBundlerClient({
    transport: http(bundlerUrl),
    entryPoint: ENTRYPOINT_ADDRESS_V07,
})

Create The Smart Wallet

To learn more about different types of wallets check out Wallets

For the purposes of this guide, we will be using Simple Account (opens in a new tab) as a smart wallet implementation. This is an ERC4337 wallet controlled by a single owner (eg. Metamask, Owl Developer Wallet).

Create Smart Wallet Owner

In this tutorial, we'll simply use a private key as the owner.

Add the following to the bottom of index.ts

/***** Create Smart Wallet Owner *****/
// Load private key from .env or generate a new one (and save it)
// Warning: Do NOT use such logic in production, for tutorial purposes ONLY
const privateKey =
    (process.env.PRIVATE_KEY as Hex) ??
    (() => {
        const pk = generatePrivateKey()
        writeFileSync(".env", `API_KEY_SECRET=${API_KEY_SECRET}\nPRIVATE_KEY=${pk}`)
        return pk
    })()
 
// Owner of the smart account
const owner = privateKeyToAccount(privateKey)

Create Smart Account

A Smart Account mimics the interface of a viem Account (opens in a new tab) but also extends it with smart wallet specific info such as how to sign (but not send) ERC4337 user operations.

We now create a Smart Account. Use signerToSimpleSmartAccount (opens in a new tab) from permissionless. We need to specify the factoryAddress we are using as well as the global ERC4337 entryPoint address. For the signer, we use the owner we just created.

Add the following to the bottom of index.ts

/***** Create Smart Account *****/
// Simple smart account owned by signer
const smartAccount = await signerToSimpleSmartAccount(publicClient, {
    signer: owner,
    factoryAddress: 0xe7A78BA9be87103C317a66EF78e6085BD74Dd538, //Simple Smart Account factory
    entryPoint: ENTRYPOINT_ADDRESS_V07,
})
 
console.log(`Smart account address: ${blockExplorer}/address/${smartAccount.address}`)

Let's run this code with npm start. You should see the smart account address printed to the console.

Smart account address: https://explorer-testnet.hedwig.build/address/<address>
💡

If you visit the address on the block explorer, you might notice that no contract is actually deployed to this address yet. This is because smart accounts are counterfactual: they are only deployed on-chain the first time you send a transaction through them.

Create Smart Account Client

A SmartAccountClient (opens in a new tab) mimics the interface of a viem Wallet Client (opens in a new tab) but under the hood will send ERC4337 user operations whenever you call sendTransaction or writeContract

Now that we have a Smart Account, we can create a Smart Account Client to send transactions. We specify the optional sponsorUserOperation middleware function, calling the sponsorUserOperation (opens in a new tab) function from the paymaster client. This will make sure that the user operation is sponsored by the Owl Protocol verifying paymaster.

Finally, we specify the gasPrice middleware function to fetch the gas price using the getUserOperationGasPrice (opens in a new tab) function from the bundler client that we will use to submit the user operation in the next step.

Add the following to the bottom of index.ts

/***** Create Smart Account Client *****/
const smartAccountClient = createSmartAccountClient({
    account: smartAccount,
    entryPoint: ENTRYPOINT_ADDRESS_V07,
    chain,
    bundlerTransport: http(bundlerUrl),
    middleware: {
        gasPrice: async () => {
            return (await bundlerClient.getUserOperationGasPrice()).fast
        },
        sponsorUserOperation: paymasterClient.sponsorUserOperation,
    },
})

Submit A Gasless Transaction

Finally, let's submit a transaction from our smart wallet. We will send a transaction to the 0xd8da6bf26964af9d7eed9e03e53415d37aa96045 (vitalik.eth) address with 0x1234 as example callData.

Under the hood, the Smart Account Client will build a user operation, request paymaster sponsorship, sign it with the smart wallet owner'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:

/***** Submit Gasless Transaction *****/
const txHash = await smartAccountClient.sendTransaction({
    to: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045", // vitalik.eth
    value: 0n,
    data: "0x1234",
})
 
console.log(`User operation included: ${blockExplorer}/tx/${txHash}`)

Let's run this code again with npm start. This should output the transaction hash that contains the user operation.

User operation included: https://explorer-testnet.hedwig.build/tx/<hash>

You can now view the transaction on the blockchain explorer.

Summary

By sending this user operation, you have:

  • Deployed a counterfactual smart account contract
  • Had this newly-deployed smart account verify the private key's signature
  • Sponsored a user operation's gas fees with the Owl Protocol verifying paymaster
  • 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 (opens in a new tab) if you have any questions or if you'd like to share what you're building!