# Tutorial 2 — Submit a user operation with an ERC-20 Paymaster

:::info
You can visit our [ERC-20 Paymaster overview page](/references/paymaster/erc20-paymaster) to learn more about the design and architecture of our ERC-20 Paymaster, and the deployed smart contract addresses.
:::

In this tutorial, you will deploy an ERC-4337 smart contract wallet on Base Sepolia, and submit a user operation that pays for its gas fees with USDC using an ERC-20 Paymaster.

:::tip[Let your coding agent do it]
Install the [Pimlico CLI](/references/platform/cli) and ask your coding agent:

> Follow docs.pimlico.io/guides/tutorials/tutorial-2: create a Pimlico API key with the pimlico CLI and send a user operation on Base Sepolia that pays its gas in USDC.
:::

## Steps

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

Either use [our dashboard](/guides/create-api-key#using-the-dashboard) or the [Pimlico CLI](/guides/create-api-key#using-the-cli-recommended).

### 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 public and bundler clients, and generate a private key

The public client will be responsible for querying the blockchain, while the bundler client will be responsible for submitting user operations for relaying.

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
const usdc = "0x036CbD53842c5426634e7929541eC2318f3dCF7e"

const privateKey =
	(process.env.PRIVATE_KEY as Hex) ??
	(() => {
		const pk = generatePrivateKey()
		writeFileSync(".env", `PRIVATE_KEY=${pk}`)
		return pk
	})()

const publicClient = createPublicClient({
	chain: baseSepolia,
	transport: http("https://sepolia.base.org"),
})

const apiKey = process.env.PIMLICO_API_KEY
const pimlicoUrl = `https://api.pimlico.io/v2/${baseSepolia.id}/rpc?apikey=${apiKey}`

const pimlicoClient = createPimlicoClient({
	chain: baseSepolia,
	transport: http(pimlicoUrl),
	entryPoint: {
		address: entryPoint07Address,
		version: "0.7" as EntryPointVersion,
	},
})
```

### Create the `SmartAccount` instance

For the purposes of this guide, we will be using [Safe](https://safe.global) accounts. This account is an ERC-4337 wallet controlled by a single EOA signer.

:::tip[Tip]
Want to learn more about using Safe accounts? Take a look at our [dedicated Safe guide](/references/permissionless/how-to/accounts/use-safe-account)
:::

To create the Safe account, we will use the `toSafeSmartAccount` utility function from permissionless.js.

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

```ts
const account = await toSafeSmartAccount({
	client: publicClient,
	owners: [privateKeyToAccount(privateKey)],
	version: "1.4.1",
})

const smartAccountClient = createSmartAccountClient({
	account,
	chain: baseSepolia,
	bundlerTransport: http(pimlicoUrl),
	paymaster: pimlicoClient,
	userOperation: {
		estimateFeesPerGas: async () => {
			return (await pimlicoClient.getUserOperationGasPrice()).fast
		},
	},
})

console.log(`Smart account address: https://sepolia.basescan.io/address/${account.address}`)
```

Since we will be looking to fund our account with USDC (which is what we will use to pay gas fees with), we need to know the address where our smart wallet will be deployed.

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

```txt
Smart account address: https://sepolia.basescan.org/address/0xbAd38BdCf884ED92ab370f69C0CD0B7b8a1459A1
```

### Get Testnet USDC on Base Sepolia

Let's get some USDC on the Base Sepolia testnet to the counterfactual address of the wallet we will be deploying. This will be used to pay for the gas fees of the user operation we will be submitting.

The recommended way to do this is to use the [USDC faucet](https://faucet.circle.com/), select 'Base Sepolia' and enter the counterfactual sender address you generated in the previous step.

### Verify you have USDC on the counterfactual sender address

To make sure you have USDC on the counterfactual sender address, let's add a check to the bottom of `index.ts`:

```ts
const senderUsdcBalance = await publicClient.readContract({
	abi: parseAbi(["function balanceOf(address account) returns (uint256)"]),
	address: usdc,
	functionName: "balanceOf",
	args: [account.address],
})

if (senderUsdcBalance < 1_000_000n) {
	throw new Error(
		`insufficient USDC balance for counterfactual wallet address ${account.address}: ${
			Number(senderUsdcBalance) / 1_000_000
		} USDC, required at least 1 USDC. Load up balance at https://faucet.circle.com/`,
	)
}

console.log("Smart account USDC balance: ", Number(senderUsdcBalance) / 1_000_000)
```

If you run this code with `npm start`, you should not see any errors, and you should see the USDC balance of the counterfactual sender address printed to the console.

```txt
Smart account USDC balance: 10 USDC
```

### Send a transaction from the smart account, paying only with USDC for gas fees.

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`. We will also specify the gas price we want to use, which we fetched from the bundler in the previous step.

Underneath the hood, the `SmartAccountClient` will build a user operation with the designated ERC-20 Paymaster, 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.

The `paymasterContext` will be passed to the paymaster endpoint, instructing it to sponsor the userOperation using ERC-20 tokens.

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

```typescript
const quotes = await pimlicoClient.getTokenQuotes({
    tokens: [usdc]
})
const paymaster = quotes[0].paymaster

const txHash = await smartAccountClient.sendTransaction({
	calls: [
		{
			to: getAddress(usdc),
			abi: parseAbi(["function approve(address,uint)"]),
			functionName: "approve",
			args: [paymaster, maxUint256],
		},
		{
			to: getAddress("0xd8da6bf26964af9d7eed9e03e53415d37aa96045"),
			data: "0x1234" as Hex,
		},
	],
	paymasterContext: {
		token: usdc,
	},
})

console.log(`transactionHash: ${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.

```txt
User operation included: https://sepolia.basescan.org/tx/0xf8e4fc41a134fc9530a0c019167f9dc0981874b90187717605355bdcce8b2fb7
```

You can now view the transaction on the Base Sepolia testnet explorer. By sending this user operation, you have:

* Deployed the counterfactual smart account contract
* Had your smart account approve the ERC-20 Paymaster to spend USDC
* Had this newly-deployed smart account verify the private key's signature
* Made an ERC-20 Paymaster sponsor the user operation's gas fees by taking USDC from the smart account
* Executed a simple transaction to `vitalik.eth`'s address

If you visit the address of the `sender` account on the [Base Sepolia explorer](https://sepolia.basescan.org), you should also see that some of your USDC balance has been deducted!

That's it! Congratulations!
::::

### 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!
