# Pay Gas with ERC-20 Tokens

Enable users to pay gas fees with ERC-20 tokens instead of native tokens using Candide's Token Paymaster.

> For the basics, see the [Getting Started Guide](https://docs.candide.dev/wallet/guides/getting-started.md).

![](/img/network-fees-sponsorship.png)

## Why Use Token Paymaster[​](#why-use-token-paymaster "Direct link to Why Use Token Paymaster")

Token paymasters enable users to pay gas fees with ERC-20 tokens instead of ETH, eliminating the need to hold native tokens for transactions.

* **Better UX**: Users transact with only the tokens they need
* **Cost Calculation**: Calculate exact token amounts for gas costs
* **Supported Tokens**: USDC, USDT, and other [popular ERC-20 tokens](https://docs.candide.dev/wallet/paymaster/tokens-supported.md)

## Quickstart[​](#quickstart "Direct link to Quickstart")

> You can also [fork the complete code](https://github.com/candidelabs/abstractionkit-examples/blob/main/pay-gas-in-erc20/pay-gas-in-erc20.ts) and follow along.

[YouTube video player](https://www.youtube.com/embed/3jU_baONS9s?si=OfWuHpqpHWbjOnJD)

### Step 1: Fetch Supported Tokens[​](#step-1-fetch-supported-tokens "Direct link to Step 1: Fetch Supported Tokens")

* index.ts
* .env

Token paymaster implementation

```
import { CandidePaymaster } from "abstractionkit";

const paymasterRPC = process.env.PAYMASTER_RPC as string;
const tokenAddress = process.env.TOKEN_ADDRESS as string;

const paymaster = new CandidePaymaster(paymasterRPC);

const tokensSupported = await paymaster.fetchSupportedERC20TokensAndPaymasterMetadata();

// Find your specific token
const tokenSelected = tokensSupported.tokens.find(
    token => token.address.toLowerCase() === tokenAddress.toLowerCase()
);

if (!tokenSelected) {
    throw new Error("Token not supported by paymaster");
}

console.log(`Using ${tokenSelected.name} (${tokenSelected.symbol}) for gas payment`);
```

Test Tokens

Get ERC-20 faucet tokens (CTT or USDT) from our [dashboard faucet](https://dashboard.candide.dev/faucet) for testing. Ensure your smart account has sufficient ERC-20 tokens to pay for gas.

```
# Paymaster service endpoint
PAYMASTER_RPC=https://api.candide.dev/public/v3/sepolia

# Token address for gas payment (CTT on Sepolia for testing)
TOKEN_ADDRESS=0xFa5854FBf9964330d761961F46565AB7326e5a3b
```

### Step2: Get Token Paymaster Data[​](#step2-get-token-paymaster-data "Direct link to Step2: Get Token Paymaster Data")

Get token paymaster data

```
if (tokenSelected) {
    userOperation = await paymaster.createTokenPaymasterUserOperation(
        smartAccount,
        userOperation,
        tokenSelected.address,
        bundlerUrl
    );

    // Calculate the cost in tokens
    const cost = await paymaster.calculateUserOperationErc20TokenMaxGasCost(
        userOperation,
        tokenSelected.address
    );
    
    console.log(`Gas cost: ${cost} wei in ${tokenSelected.symbol}`);
    console.log(`Make sure your account has enough ${tokenSelected.symbol} tokens`);
}
```

## Complete Runnable Example[​](#complete-runnable-example "Direct link to Complete Runnable Example")

Below is a complete example that demonstrates ERC-20 token gas payments:

Full Working Example

* index.ts
* .env

```
import * as dotenv from 'dotenv'

import {
    SafeAccountV0_3_0 as SafeAccount,
    MetaTransaction,
    CandidePaymaster,
    getFunctionSelector,
    createCallData,
} from "abstractionkit";

async function main(): Promise<void> {
    // Load environment variables
    dotenv.config()
    const chainId = BigInt(process.env.CHAIN_ID as string)
    const bundlerUrl = process.env.BUNDLER_URL as string
    const jsonRpcNodeProvider = process.env.JSON_RPC_NODE_PROVIDER as string
    const paymasterRPC = process.env.PAYMASTER_RPC as string
    const tokenAddress = process.env.TOKEN_ADDRESS as string
    const ownerPublicAddress = process.env.PUBLIC_ADDRESS as string
    const ownerPrivateKey = process.env.PRIVATE_KEY as string
    
    // Create smart account
    let smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress])
    console.log("Smart Account Address:", smartAccount.accountAddress)

    // Create example transactions (minting NFTs)
    const nftContractAddress = "0x9a7af758aE5d7B6aAE84fe4C5Ba67c041dFE5336";
    const mintFunctionSignature = 'mint(address)';
    const mintFunctionSelector = getFunctionSelector(mintFunctionSignature);
    const mintTransactionCallData = createCallData(
        mintFunctionSelector, 
        ["address"],
        [smartAccount.accountAddress]
    );
    
    const transaction1: MetaTransaction = {
        to: nftContractAddress,
        value: 0n,
        data: mintTransactionCallData,
    }

    const transaction2: MetaTransaction = {
        to: nftContractAddress,
        value: 0n,
        data: mintTransactionCallData,
    }

    // Create UserOperation
    let userOperation = await smartAccount.createUserOperation(
        [transaction1, transaction2],
        jsonRpcNodeProvider,
        bundlerUrl
    )

    // Set up token paymaster
    const paymaster = new CandidePaymaster(paymasterRPC)
    
    // Get supported tokens and find the one we want to use
    const tokensSupported = await paymaster.fetchSupportedERC20TokensAndPaymasterMetadata();
    const tokenSelected = tokensSupported.tokens.find(
        token => token.address.toLowerCase() === tokenAddress.toLowerCase()
    );

    if (!tokenSelected) {
        throw new Error("Token not supported by paymaster");
    }

    console.log(`💰 Using ${tokenSelected.name} (${tokenSelected.symbol}) for gas payment`);

    // Create token paymaster UserOperation
    userOperation = await paymaster.createTokenPaymasterUserOperation(
        smartAccount,
        userOperation,
        tokenSelected.address,
        bundlerUrl
    );

    // Calculate the cost in tokens
    const cost = await paymaster.calculateUserOperationErc20TokenMaxGasCost(
        userOperation,
        tokenSelected.address
    );
    
    console.log(`⛽ Gas cost: ${cost} wei in ${tokenSelected.symbol}`);
    console.log(`🔍 Make sure account has enough ${tokenSelected.symbol} tokens`);

    // Sign the UserOperation
    userOperation.signature = smartAccount.signUserOperation(
        userOperation,
        [ownerPrivateKey],
        chainId
    )

    // Submit the UserOperation
    const sendUserOperationResponse = await smartAccount.sendUserOperation(
        userOperation, 
        bundlerUrl
    )

    console.log("📤 UserOperation sent. Waiting for confirmation...")
    
    // Wait for transaction to be included
    let userOperationReceiptResult = await sendUserOperationResponse.included()

    console.log("📋 UserOperation receipt received.")
    console.log(userOperationReceiptResult)
    
    if (userOperationReceiptResult.success) {
        console.log("🎉 Transaction successful! Hash:", userOperationReceiptResult.receipt.transactionHash)
        console.log(`💸 Gas paid with ${tokenSelected.symbol} tokens`);
    } else {
        console.log("❌ UserOperation execution failed")
    }
}

main().catch(console.error)
```

```
CHAIN_ID=11155111
BUNDLER_URL=https://api.candide.dev/public/v3/sepolia
JSON_RPC_NODE_PROVIDER=https://ethereum-sepolia-rpc.publicnode.com

# Paymaster configuration
PAYMASTER_RPC=https://api.candide.dev/public/v3/sepolia
TOKEN_ADDRESS=0xFa5854FBf9964330d761961F46565AB7326e5a3b

# Your EOA credentials
PRIVATE_KEY=your_private_key_here
PUBLIC_ADDRESS=your_public_address_here
```
