<VersionWarning version="0.1" />

# Tutorial 2 — Submit a user operation with a Verifying Paymaster

:::note
This is a low-level tutorial that walks you through the steps of constructing a user operation from scratch. If you would like to leverage permissionless.js's high-level functions, take a look at [tutorial 1](/references/permissionless/tutorial/tutorial-1).
:::

In this tutorial, you will generate a user operation, ask Pimlico's verifying paymaster to sponsor it, and then submit the sponsored user operation on-chain with Pimlico's Alto bundler.

## Steps

:::steps
### Get a Pimlico API key

[Create your API key](/guides/create-api-key)

### Clone the Pimlico tutorial template repository

We have created a [Pimlico tutorial template repository](https://github.com/pimlicolabs/tutorial-template) that you can use to get started. It comes set up with Typescript, viem, and permissionless.js.

```bash
git clone https://github.com/pimlicolabs/tutorial-template.git pimlico-tutorial-2
cd pimlico-tutorial-2
```

Now, let's install the dependencies:

```bash
npm install
```

The main file we will be working with is `index.ts`. Let's run it to make sure everything is working:

```bash
npm start
```

If everything has been set up correctly, you should see `Hello world!` printed to the console.

### Create the viem clients

We will be using three different clients for this example.

1. Standard publicClient for normal Ethereum RPC calls — https://sepolia.rpc.thirdweb.com
2. Pimlico v1 api for the Bundler methods — https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR\_PIMLICO\_API\_KEY
3. Pimlico v2 api for the Paymaster methods — https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR\_PIMLICO\_API\_KEY

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:

```ts
export const publicClient = createPublicClient({
	transport: http(),
	chain: sepolia,
})

const apiKey = "YOUR_PIMLICO_API_KEY" // REPLACE THIS
const endpointUrl = `https://api.pimlico.io/v2/sepolia/rpc?apikey=${apiKey}`

const bundlerClient = createClient({
	transport: http(endpointUrl, {
		timeout: 30_000,
	}),
	chain: sepolia,
})
	.extend(bundlerActions(ENTRYPOINT_ADDRESS_V07))
	.extend(pimlicoBundlerActions(ENTRYPOINT_ADDRESS_V07))

const paymasterClient = createClient({
	transport: http(endpointUrl),
	chain: sepolia,
}).extend(pimlicoPaymasterActions(ENTRYPOINT_ADDRESS_V07))
```

### Generate the factory and factoryData

For the purposes of this guide, we will be using the [SimpleAccount.sol](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/accounts/SimpleAccount.sol) wallet found in the eth-infinitism repository. This Wallet is a simple ERC-4337 wallet controlled by a single EOA signer.

At `0x91E60e0613810449d098b0b5Ec8b51A0FE8c8985`, most chain already have deployed a [SimpleAccountFactory.sol](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/accounts/SimpleAccountFactory.sol) contract, that is able to easily deploy new SimpleAccount instances via the `createAccount` function. We will be leveraging this contract to help us generate the `factory` and `factoryData`.

Requesting a smart contract deployment is done through the `factory` and `factoryData` field, where the `factory` field specifies the address the EntryPoint will call, and the `factoryData` corresponds to the data that will be called on that factory.

Add the following to the bottom of `index.ts`:

```ts
const SIMPLE_ACCOUNT_FACTORY_ADDRESS = "0x91E60e0613810449d098b0b5Ec8b51A0FE8c8985"

const ownerPrivateKey = generatePrivateKey()
const owner = privateKeyToAccount(ownerPrivateKey)

console.log("Generated wallet with private key:", ownerPrivateKey)

const factory = SIMPLE_ACCOUNT_FACTORY_ADDRESS
const factoryData = encodeFunctionData({
	abi: [
		{
			inputs: [
				{ name: "owner", type: "address" },
				{ name: "salt", type: "uint256" },
			],
			name: "createAccount",
			outputs: [{ name: "ret", type: "address" }],
			stateMutability: "nonpayable",
			type: "function",
		},
	],
	args: [owner.address, 0n],
})

console.log("Generated factoryData:", factoryData)
```

Let's run this code with `npm start`. You should see the generated initCode printed to the console.

```txt
Generated factoryData: 0x9406cc6185a346906296840746125a0e449764545fbfb9cf000000000000000000000000508a7005f997a21cd662d6fb41ad69f945c129c10000000000000000000000000000000000000000000000000000000000000000
```

### Calculate the sender address

Now that we have the `factory` and `factoryData`, we have to calculate the corresponding `sender` address, which is the address the `SimpleAccount` will be deployed to, and thereby the address which will handle the verification and execution steps of the UserOperation.

We do this by calling the `getSenderAddress` utility function on the EntryPoint. Upon success, it will revert with a special error type that contains the counterfactual address of the smart contract wallet that will be deployed

Add the following to the bottom of `index.ts`:

```typescript
const senderAddress = await getSenderAddress(publicClient, {
	factory,
	factoryData,
	entryPoint: ENTRYPOINT_ADDRESS_V07,
})
console.log("Calculated sender address:", senderAddress)
```

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

```txt
Calculated sender address: 0xbAd38BdCf884ED92ab370f69C0CD0B7b8a1459A1
```

### Generate the callData

Now, let's decide on the `callData` that we want the wallet to actually execute once the UserOperation passes verification.

Add the following to the bottom of `index.ts`:

```typescript
const to = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" // vitalik
const value = 0n
const data = "0x68656c6c6f" // "hello" encoded to utf-8 bytes

const callData = encodeFunctionData({
	abi: [
		{
			inputs: [
				{ name: "dest", type: "address" },
				{ name: "value", type: "uint256" },
				{ name: "func", type: "bytes" },
			],
			name: "execute",
			outputs: [],
			stateMutability: "nonpayable",
			type: "function",
		},
	],
	args: [to, value, data],
})

console.log("Generated callData:", callData)
```

Above, we are leveraging the `execute` function of the SimpleWallet, which simply calls an arbitrary address, with arbitrary value and arbitrary callData.

Let's run this code with `npm start`. You should see the callData printed to the console.

```txt
Generated callData: 0xb61d27f6000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa9604500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000568656c6c6f000000000000000000000000000000000000000000000000000000
```

### Fill out remaining UserOperation values

We're almost there, now let's fill out the rest of the UserOperation values.

Add the following to the bottom of `index.ts`:

```typescript
const gasPrice = await bundlerClient.getUserOperationGasPrice()

const userOperation = {
	sender: senderAddress,
	nonce: 0n,
	factory: factory as Address,
	factoryData,
	callData,
	maxFeePerGas: gasPrice.fast.maxFeePerGas,
	maxPriorityFeePerGas: gasPrice.fast.maxPriorityFeePerGas,
	// dummy signature, needs to be there so the SimpleAccount doesn't immediately revert because of invalid signature length
	signature:
		"0xa15569dd8f8324dbeabf8073fdec36d4b754f53ce5901e283c6de79af177dc94557fa3c9922cd7af2a96ca94402d35c39f266925ee6407aeb32b31d76978d4ba1c" as Hex,
}
```

### Request Pimlico verifying paymaster sponsorship

To make the operation gasless, we will leverage Pimlico's verifying paymaster. Using paymasters allows you to delegate the gas fee payment to a third-party contract that can decide whether it is willing to pay the gas fees for the user operation. In this case, Pimlico's verifying paymaster checks whether its off-chain signer has signed off on the user operation. To request Pimlico's signer to sign your user operation, call the `pm_sponsorUserOperation` endpoint or use the `sponsorUserOperation` method from the permissionless.js Pimlico paymaster actions.

Add the following to the bottom of `index.ts`:

```typescript
const sponsorUserOperationResult = await paymasterClient.sponsorUserOperation({
	userOperation,
})

const sponsoredUserOperation: UserOperation<"v0.7"> = {
	...userOperation,
	...sponsorUserOperationResult,
}

console.log("Received paymaster sponsor result:", sponsorUserOperationResult)
```

Let's run this code with `npm start`. You should see something like this:

```txt
Received paymaster sponsor result: {
  paymaster: '0xcF60744ef322396a6d0a5B7d396F5814176855F1',
  paymasterVerificationGasLimit: 526114n,
  paymasterPostOpGasLimit: 75900n,
  paymasterData: '000000000000000000000000000000000000000000000000000000006518a0c100000000000000000000000000000000000000000000000000000000000000003251910f0e14691ca19a8e2cca216ce77a1ce8d002e975e113cfbc36d8e489a550dbb24ff485ee61c1b91cf8e20261d307ef79f22cf9359fba3271f721dade4d1b',
  preVerificationGas: 247487n,
  verificationGasLimit: 526114n,
  callGasLimit: 75900n
}
```

Great! Now we have received the gas limit estimates and added the paymaster-related fields containing Pimlico's signature to our UserOperation.

### Sign the UserOperation

The last field to fill out is the `signature`. This is a simple ECDSA signature consistent with typical Ethereum private key signing procedures.

Add the following to the bottom of `index.ts`:

```typescript
const signature = await signUserOperationHashWithECDSA({
	account: owner,
	userOperation: sponsoredUserOperation,
	chainId: sepolia.id,
	entryPoint: ENTRYPOINT_ADDRESS_V07,
})
sponsoredUserOperation.signature = signature

console.log("Generated signature:", signature)
```

Let's run this code with `npm start`. You should see something like this:

```txt
Generated signature: 0xcaae357fcd3882f1ea4b48f7dcce9d7f2482794ab72d1075ce5d7fcef4c5ec03265fe03e5fd7f8af65a4cd05b7e01300f3938f7e245dc8038748ddef93d5f4061c
```

### Submit the UserOperation to be bundled

Finally, we're ready to submit the UserOperation to Pimlico's bundler, which will include it on-chain. The `eth_sendUserOperation` RPC call or the `sendUserOperation` method from permissionless.js will submit the UserOperation to the bundler.

You can also query for receipts to keep checking the status of the UserOperation until it is included.

Add the following to the bottom of `index.ts`:

```typescript
const userOperationHash = await bundlerClient.sendUserOperation({
	userOperation: sponsoredUserOperation,
})

console.log("Received User Operation hash:", userOperationHash)

// let's also wait for the userOperation to be included, by continually querying for the receipts
console.log("Querying for receipts...")
const receipt = await bundlerClient.waitForUserOperationReceipt({
	hash: userOperationHash,
})
const txHash = receipt.receipt.transactionHash

console.log(`UserOperation included: https://sepolia.etherscan.io/tx/${txHash}`)
```

If we run this code with `npm start`, we will go through the whole flow of executing the User Operation. You should see something like this:

```txt
UserOperation included: https://sepolia.etherscan.io/tx/0xe0a47e0e6637d0d4062cda3683afe2f5809bf545b917888277905de38f08e188
```

Once the UserOperation is included, you can view the transaction on the Sepolia testnet explorer.

That's it! You've successfully generated a UserOperation and submitted it using Pimlico's Alto bundler.

By leveraging Pimlico's paymaster, you were able to make the User Operation completely gasless, and by using Pimlico's Alto bundler, you were able to submit the User Operation to the chain without having to worry about maintaining your own relaying infrastructure.

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](https://github.com/pimlicolabs/tutorials). If you're looking to run it, remember to replace the API key with your own!
