# How to send USDC without ETH for gas

This guide shows you how to let users send USDC from an account that holds no ETH, with either the user or your app paying the network fee. If the user's USDC is in an EOA, also follow [Send from an existing EOA with EIP-7702](#send-from-an-existing-eoa-with-eip-7702).

This guide uses Base. For other chains, check the [supported tokens](/references/paymaster/erc20-paymaster/supported-tokens). To try it on Base Sepolia first, follow [Tutorial 2](/guides/tutorials/tutorial-2), which uses testnet USDC from the Circle faucet.

<FeePayerSelector />

## Installation

:::code-group
```bash [npm]
npm install permissionless viem
```

```bash [pnpm]
pnpm install permissionless viem
```

```bash [bun]
bun install permissionless viem
```
:::

You need a Pimlico API key. [Create one here](/guides/create-api-key).

## Steps

<WhenFeePayer is="user">
  :::steps
  ### Create the clients

  The Pimlico client is both the bundler and the paymaster. Passing `prepareUserOperationForErc20Paymaster` as the smart account client's `prepareUserOperation` makes it add the paymaster approval whenever `paymasterContext` contains a `token`.

  ```ts
  import { createSmartAccountClient } from "permissionless"
  import { toSimpleSmartAccount } from "permissionless/accounts"
  import { createPimlicoClient } from "permissionless/clients/pimlico"
  import { prepareUserOperationForErc20Paymaster } from "permissionless/experimental/pimlico"
  import { createPublicClient, type Hex, http } from "viem"
  import { entryPoint07Address } from "viem/account-abstraction"
  import { privateKeyToAccount } from "viem/accounts"
  import { base } from "viem/chains"

  const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" // USDC on Base
  const USDC_DECIMALS = 6

  const pimlicoUrl = `https://api.pimlico.io/v2/${base.id}/rpc?apikey=YOUR_PIMLICO_API_KEY`

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

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

  // The user's signer. In production this is usually an embedded wallet or passkey signer.
  const owner = privateKeyToAccount(process.env.PRIVATE_KEY as Hex)

  const account = await toSimpleSmartAccount({
  	client: publicClient,
  	owner,
  	entryPoint: {
  		address: entryPoint07Address,
  		version: "0.7",
  	},
  })

  const smartAccountClient = createSmartAccountClient({
  	account,
  	chain: base,
  	bundlerTransport: http(pimlicoUrl),
  	paymaster: pimlicoClient,
  	userOperation: {
  		estimateFeesPerGas: async () => {
  			return (await pimlicoClient.getUserOperationGasPrice()).fast
  		},
  		// Injects the exact USDC approval for the ERC-20 Paymaster when it is needed
  		prepareUserOperation: prepareUserOperationForErc20Paymaster(pimlicoClient),
  	},
  })
  ```

  The account address is counterfactual: it's known before the account is deployed, and the account is deployed by its first user operation. Send the user's USDC to `account.address`.

  ### Show the fee before the user confirms

  Prepare the user operation with the USDC transfer and `paymasterContext: { token: USDC }`, then convert its maximum gas cost into USDC with `estimateErc20PaymasterCost`. `costInToken` is in USDC base units (6 decimals) and `costInUsd` always has 6 decimals.

  ```ts
  import { encodeFunctionData, erc20Abi, formatUnits, parseUnits } from "viem"

  const recipient = "0x1111111111111111111111111111111111111111" // replace with the recipient's address
  const amount = parseUnits("25", USDC_DECIMALS) // 25 USDC

  const transferCall = {
  	to: USDC,
  	value: 0n,
  	data: encodeFunctionData({
  		abi: erc20Abi,
  		functionName: "transfer",
  		args: [recipient, amount],
  	}),
  } as const

  const userOperation = await smartAccountClient.prepareUserOperation({
  	calls: [transferCall],
  	paymasterContext: { token: USDC },
  })

  const { costInToken, costInUsd } = await pimlicoClient.estimateErc20PaymasterCost({
  	userOperation,
  	token: USDC,
  })

  console.log(`Max network fee: ${formatUnits(costInToken, USDC_DECIMALS)} USDC (~$${formatUnits(costInUsd, 6)})`)
  ```

  The estimate is the most the paymaster can charge for this user operation. The amount actually charged is computed from the gas used, so it's usually lower.

  ### Check the balance covers the amount and the fee

  The paymaster collects USDC from the sender after the transfer executes. If the account sends its entire balance, nothing is left for the fee and the user operation fails simulation. For a "send max" button, send the balance minus the fee estimate.

  ```ts
  const balance = await publicClient.readContract({
  	address: USDC,
  	abi: erc20Abi,
  	functionName: "balanceOf",
  	args: [account.address],
  })

  if (balance < amount + costInToken) {
  	throw new Error(
  		`Insufficient USDC: need ${formatUnits(amount + costInToken, USDC_DECIMALS)}, have ${formatUnits(balance, USDC_DECIMALS)}`,
  	)
  }
  ```

  ### Send the transfer and wait for the receipt

  ```ts
  const userOpHash = await smartAccountClient.sendUserOperation({
  	calls: [transferCall],
  	paymasterContext: { token: USDC },
  })

  const receipt = await smartAccountClient.waitForUserOperationReceipt({
  	hash: userOpHash,
  })

  if (!receipt.success) {
  	throw new Error(`User operation reverted: ${receipt.reason ?? "unknown reason"}`)
  }

  console.log(`Sent 25 USDC: https://basescan.org/tx/${receipt.receipt.transactionHash}`)
  console.log(`Fee paid in USDC, actual gas cost: ${receipt.actualGasCost} wei`)
  ```

  Check `receipt.success` before you show the transfer as sent. If the account's allowance for the paymaster is too low, the user operation approves the maximum fee before the transfer, in the same batch, so the user still signs once.
  :::
</WhenFeePayer>

<WhenFeePayer is="app">
  :::steps
  ### Create the clients

  The Pimlico client is both the bundler and the paymaster.

  ```ts
  import { createSmartAccountClient } from "permissionless"
  import { toSimpleSmartAccount } from "permissionless/accounts"
  import { createPimlicoClient } from "permissionless/clients/pimlico"
  import { createPublicClient, encodeFunctionData, erc20Abi, type Hex, http, parseUnits } from "viem"
  import { entryPoint07Address } from "viem/account-abstraction"
  import { privateKeyToAccount } from "viem/accounts"
  import { base } from "viem/chains"

  const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" // USDC on Base
  const USDC_DECIMALS = 6

  const pimlicoUrl = `https://api.pimlico.io/v2/${base.id}/rpc?apikey=YOUR_PIMLICO_API_KEY`

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

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

  // The user's signer. In production this is usually an embedded wallet or passkey signer.
  const owner = privateKeyToAccount(process.env.PRIVATE_KEY as Hex)

  const account = await toSimpleSmartAccount({
  	client: publicClient,
  	owner,
  	entryPoint: {
  		address: entryPoint07Address,
  		version: "0.7",
  	},
  })

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

  The account address is counterfactual: it's known before the account is deployed, and the account is deployed by its first user operation. Send the user's USDC to `account.address`.

  ### Send the transfer and wait for the receipt

  Send the transfer without a `paymasterContext`. The verifying paymaster sponsors it and charges your Pimlico balance, so the user only needs the USDC they're sending. To cap your spend per user or in total, pass a `sponsorshipPolicyId` ([sponsorship policies](/guides/how-to/sponsorship-policies)).

  ```ts
  const recipient = "0x1111111111111111111111111111111111111111" // replace with the recipient's address
  const amount = parseUnits("25", USDC_DECIMALS) // 25 USDC

  // No paymasterContext: the verifying paymaster sponsors it and charges your Pimlico balance
  const userOpHash = await smartAccountClient.sendUserOperation({
  	calls: [
  		{
  			to: USDC,
  			value: 0n,
  			data: encodeFunctionData({
  				abi: erc20Abi,
  				functionName: "transfer",
  				args: [recipient, amount],
  			}),
  		},
  	],
  })

  const receipt = await smartAccountClient.waitForUserOperationReceipt({
  	hash: userOpHash,
  })

  if (!receipt.success) {
  	throw new Error(`User operation reverted: ${receipt.reason ?? "unknown reason"}`)
  }

  console.log(`Sent 25 USDC: https://basescan.org/tx/${receipt.receipt.transactionHash}`)
  ```

  Check `receipt.success` before you show the transfer as sent.
  :::

  To sponsor only a few transfers per user per day and charge the user in USDC after that, follow [How to send USDC remittances without ETH for gas](/guides/how-to/payments/remittances).
</WhenFeePayer>

## Send from an existing EOA with EIP-7702

If the user's USDC is in an EOA, delegate the EOA to a Simple account implementation with EIP-7702 instead of moving funds to a new address. The address stays the same; the account uses EntryPoint 0.8, so the Pimlico client is configured for 0.8 too.

<WhenFeePayer is="user">
  ```ts
  import { createSmartAccountClient } from "permissionless"
  import { to7702SimpleSmartAccount } from "permissionless/accounts"
  import { createPimlicoClient } from "permissionless/clients/pimlico"
  import { prepareUserOperationForErc20Paymaster } from "permissionless/experimental/pimlico"
  import { createPublicClient, encodeFunctionData, erc20Abi, type Hex, http, parseUnits } from "viem"
  import { entryPoint08Address } from "viem/account-abstraction"
  import { privateKeyToAccount } from "viem/accounts"
  import { base } from "viem/chains"

  const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" // USDC on Base
  const SIMPLE_7702_IMPLEMENTATION = "0xe6Cae83BdE06E4c305530e199D7217f42808555B"

  const pimlicoUrl = `https://api.pimlico.io/v2/${base.id}/rpc?apikey=YOUR_PIMLICO_API_KEY`

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

  const pimlicoClient = createPimlicoClient({
  	chain: base,
  	transport: http(pimlicoUrl),
  	entryPoint: {
  		address: entryPoint08Address,
  		version: "0.8",
  	},
  })

  // The user's existing EOA. It must be a signer that can sign EIP-7702 authorizations.
  const eoa = privateKeyToAccount(process.env.EOA_PRIVATE_KEY as Hex)

  // The smart account address is the EOA's own address
  const account = await to7702SimpleSmartAccount({
  	client: publicClient,
  	owner: eoa,
  })

  const smartAccountClient = createSmartAccountClient({
  	account,
  	chain: base,
  	bundlerTransport: http(pimlicoUrl),
  	paymaster: pimlicoClient,
  	userOperation: {
  		estimateFeesPerGas: async () => {
  			return (await pimlicoClient.getUserOperationGasPrice()).fast
  		},
  		prepareUserOperation: prepareUserOperationForErc20Paymaster(pimlicoClient),
  	},
  })
  ```
</WhenFeePayer>

<WhenFeePayer is="app">
  ```ts
  import { createSmartAccountClient } from "permissionless"
  import { to7702SimpleSmartAccount } from "permissionless/accounts"
  import { createPimlicoClient } from "permissionless/clients/pimlico"
  import { createPublicClient, encodeFunctionData, erc20Abi, type Hex, http, parseUnits } from "viem"
  import { entryPoint08Address } from "viem/account-abstraction"
  import { privateKeyToAccount } from "viem/accounts"
  import { base } from "viem/chains"

  const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" // USDC on Base
  const SIMPLE_7702_IMPLEMENTATION = "0xe6Cae83BdE06E4c305530e199D7217f42808555B"

  const pimlicoUrl = `https://api.pimlico.io/v2/${base.id}/rpc?apikey=YOUR_PIMLICO_API_KEY`

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

  const pimlicoClient = createPimlicoClient({
  	chain: base,
  	transport: http(pimlicoUrl),
  	entryPoint: {
  		address: entryPoint08Address,
  		version: "0.8",
  	},
  })

  // The user's existing EOA. It must be a signer that can sign EIP-7702 authorizations.
  const eoa = privateKeyToAccount(process.env.EOA_PRIVATE_KEY as Hex)

  // The smart account address is the EOA's own address
  const account = await to7702SimpleSmartAccount({
  	client: publicClient,
  	owner: eoa,
  })

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

Sign an EIP-7702 authorization and attach it to the first user operation only. Once the EOA is delegated, `account.isDeployed()` returns `true` and you can omit it.

<WhenFeePayer is="user">
  ```ts
  const transferCall = {
  	to: USDC,
  	value: 0n,
  	data: encodeFunctionData({
  		abi: erc20Abi,
  		functionName: "transfer",
  		args: ["0x1111111111111111111111111111111111111111", parseUnits("25", 6)],
  	}),
  } as const

  // The EOA only has code once its EIP-7702 delegation has landed
  const isDelegated = await account.isDeployed()

  const txHash = await smartAccountClient.sendTransaction({
  	calls: [transferCall],
  	paymasterContext: { token: USDC },
  	// Only the first user operation needs to carry the signed authorization
  	authorization: isDelegated
  		? undefined
  		: await eoa.signAuthorization({
  				address: SIMPLE_7702_IMPLEMENTATION,
  				chainId: base.id,
  				nonce: await publicClient.getTransactionCount({ address: eoa.address }),
  			}),
  })

  console.log(`https://basescan.org/tx/${txHash}`)
  ```
</WhenFeePayer>

<WhenFeePayer is="app">
  ```ts
  const transferCall = {
  	to: USDC,
  	value: 0n,
  	data: encodeFunctionData({
  		abi: erc20Abi,
  		functionName: "transfer",
  		args: ["0x1111111111111111111111111111111111111111", parseUnits("25", 6)],
  	}),
  } as const

  // The EOA only has code once its EIP-7702 delegation has landed
  const isDelegated = await account.isDeployed()

  const txHash = await smartAccountClient.sendTransaction({
  	calls: [transferCall],
  	// Only the first user operation needs to carry the signed authorization
  	authorization: isDelegated
  		? undefined
  		: await eoa.signAuthorization({
  				address: SIMPLE_7702_IMPLEMENTATION,
  				chainId: base.id,
  				nonce: await publicClient.getTransactionCount({ address: eoa.address }),
  			}),
  })

  console.log(`https://basescan.org/tx/${txHash}`)
  ```
</WhenFeePayer>

The signer has to support signing EIP-7702 authorizations. viem local accounts can; for an embedded wallet, check its provider's documentation. For browser extension wallets, see [external wallets](/guides/eip7702/external). The [EIP-7702 quickstart](/guides/eip7702/demo) walks through the same flow on Sepolia.

## Notes

<WhenFeePayer is="user">
  * **Approval strategy**: `prepareUserOperationForErc20Paymaster` approves only the maximum fee of each user operation. If you prefer a one-time unlimited approval, see [using the ERC-20 Paymaster with unlimited approvals](/guides/how-to/erc20-paymaster/how-to/use-paymaster-with-unlimited-approval).
  * **Sponsorship window**: an ERC-20 Paymaster sponsorship is valid for 10 minutes, so prepare and send the user operation in the same session.
  * **API keys**: in a client app, route Pimlico requests through your backend instead of embedding the key ([guide](/guides/how-to/security/protect-api-keys)).
</WhenFeePayer>

<WhenFeePayer is="app">
  * **API keys**: in a client app, route Pimlico requests through your backend instead of embedding the key ([guide](/guides/how-to/security/protect-api-keys)).
</WhenFeePayer>

## Related

<WhenFeePayer is="user">
  * [How gasless stablecoin payments work](/guides/conceptual/gasless-stablecoin-payments)
  * [Tutorial 2: submit a user operation with an ERC-20 Paymaster](/guides/tutorials/tutorial-2)
  * [How to use the ERC-20 Paymaster](/guides/how-to/erc20-paymaster/how-to/use-paymaster)
  * [Estimate token cost and cost in USD](/guides/how-to/erc20-paymaster/how-to/calculate-costs)
  * [ERC-20 Paymaster contract addresses](/references/paymaster/erc20-paymaster/contract-addresses)
  * [`to7702SimpleSmartAccount` reference](/references/permissionless/reference/accounts/to7702SimpleSmartAccount)
</WhenFeePayer>

<WhenFeePayer is="app">
  * [How gasless stablecoin payments work](/guides/conceptual/gasless-stablecoin-payments)
  * [Sponsorship policies](/guides/how-to/sponsorship-policies)
  * [How to send USDC remittances without ETH for gas](/guides/how-to/payments/remittances)
  * [`to7702SimpleSmartAccount` reference](/references/permissionless/reference/accounts/to7702SimpleSmartAccount)
</WhenFeePayer>
