> ## Documentation Index
> Fetch the complete documentation index at: https://docs.startale.com/llms.txt
> Use this file to discover all available pages before exploring further.

# ERC-20 paymaster

> Pay gas in ERC-20 tokens through the Startale Token Paymaster.

The ERC-20 paymaster flow lets users pay gas in tokens like ASTR or USDC instead of holding native ETH. The Startale Token Paymaster quotes the cost in tokens, the user (or your dapp) approves the spend once, and every subsequent UserOperation charges the token directly.

<Note>
  Source: [`StartaleGroup/scs-aa-sdk`](https://github.com/StartaleGroup/scs-aa-sdk). Token addresses for each network are listed in [Supported networks](/aa-sdk/resources/supported-networks).
</Note>

## How it works

```mermaid theme={null}
sequenceDiagram
  participant App
  participant Client as StartaleAccountClient
  participant PM as Token Paymaster
  participant Bundler as SCS Bundler
  participant EP as EntryPoint v0.7

  App->>Client: getTokenPaymasterQuotes(tokens, userOp)
  Client->>PM: token quote RPC
  PM-->>Client: feeQuotes (per token)
  App->>Client: sendTokenPaymasterUserOp(calls, token)
  Client->>PM: pm_getPaymasterStubData (token)
  PM-->>Client: stub paymasterAndData
  Client->>Bundler: eth_estimateUserOperationGas
  Bundler-->>Client: gas limits
  Client->>PM: pm_getPaymasterData (token)
  PM-->>Client: paymasterAndData
  Note over Client: sign populated UserOp
  Client->>Bundler: eth_sendUserOperation
  Bundler->>EP: handleOps
```

## 1. Build the token-paymaster client

The client looks like the sponsored client, except `paymasterContext.token` replaces `paymasterContext.paymasterId`.

```ts theme={null}
import { http } from "viem"
import {
  createSCSPaymasterClient,
  createSmartAccountClient,
} from "@startale-scs/aa-sdk"

const paymasterClient = createSCSPaymasterClient({
  transport: http(process.env.PAYMASTER_URL!),
})

const tokenAccountClient = createSmartAccountClient({
  account,
  transport: http(process.env.BUNDLER_URL!),
  client: publicClient,
  paymaster: paymasterClient,
  paymasterContext: {
    token: "0xfF0CBFbA43a1Ce2B8d72B2f3121558BcBd4B03a6", // USDC on Soneium Minato
  },
})
```

| Field                    | Type      | Notes                                                                                                                          |
| ------------------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `paymasterContext.token` | `Address` | The ERC-20 used to pay gas. Must be one of the [supported tokens](/aa-sdk/resources/supported-networks) on the target network. |

## 2. Quote the cost in tokens

Before sending a UserOperation, ask the paymaster for the cost across the tokens you support. `getTokenPaymasterQuotes` returns one entry per token plus a list of unsupported addresses you passed in.

```ts theme={null}
const quotes = await tokenAccountClient.getTokenPaymasterQuotes({
  userOp: {
    calls: [{ to: targetAddress, data: callData, value: 0n }],
  },
  tokens: [
    "0xfF0CBFbA43a1Ce2B8d72B2f3121558BcBd4B03a6", // USDC
    "0x26e6f7c7047252DdE3dcBF26AA492e6a264Db655", // ASTR
  ],
})

for (const quote of quotes.feeQuotes) {
  console.log(quote.symbol, quote.maxGasFee, quote.maxGasFeeUSD, quote.exchangeRate)
}
```

| Field on each `feeQuote`                       | What it means                                                   |
| ---------------------------------------------- | --------------------------------------------------------------- |
| `symbol`, `decimal`, `tokenAddress`, `logoUrl` | Display metadata for the token.                                 |
| `maxGasFee`                                    | Maximum cost in token base units (already factoring `decimal`). |
| `maxGasFeeUSD`                                 | The same maximum, denominated in USD for display.               |
| `exchangeRate`                                 | Token-to-native rate the paymaster used for the quote.          |
| `premiumPercentage`                            | Spread the paymaster adds on top of the raw conversion.         |
| `validUntil`                                   | Unix timestamp; quote expires after this point.                 |

<Tip>
  Show the user a token picker built from `feeQuotes`. Disable the entries in `quotes.unsupportedTokens` so they cannot be selected.
</Tip>

## 3. Send the UserOperation

Once the user picks a token, send the UserOperation with the token-paymaster client. The SDK takes care of the approval flow and packing the token into `paymasterAndData`.

```ts theme={null}
const hash = await tokenAccountClient.sendTokenPaymasterUserOp({
  calls: [
    {
      to: targetAddress,
      data: callData,
      value: 0n,
    },
  ],
  feeTokenAddress: chosenTokenAddress,
})

const receipt = await tokenAccountClient.waitForUserOperationReceipt({ hash })
```

| Symbol                        | Source                 | Role                                                                                                                      |
| ----------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `sendTokenPaymasterUserOp`    | `@startale-scs/aa-sdk` | One-shot helper that approves the token (if needed), prepares the UserOperation with the token paymaster, and submits it. |
| `prepareTokenPaymasterUserOp` | `@startale-scs/aa-sdk` | Lower-level helper that returns the unsigned UserOperation if you need to inspect it before signing.                      |

## Common errors

| Error                    | Likely cause                                                         | Fix                                                                                     |
| ------------------------ | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| `unsupported token`      | The token is not listed for the network.                             | Use one of the addresses in [Supported networks](/aa-sdk/resources/supported-networks). |
| `insufficient allowance` | The smart account has not approved the paymaster to spend the token. | Use `sendTokenPaymasterUserOp`, which inserts the approval automatically.               |
| `quote expired`          | The user took longer than `validUntil` to sign.                      | Re-quote and re-prompt.                                                                 |

## Next steps

<CardGroup cols={2}>
  <Card title="Sponsored paymaster" icon="gas-pump" href="/aa-sdk/tutorials/sponsored-tx">
    Compare token gas to a fully sponsored flow.
  </Card>

  <Card title="Supported tokens" icon="folder" href="/aa-sdk/resources/supported-networks">
    Look up the exact token address for the network you target.
  </Card>

  <Card title="Contract interactions" icon="layer-group" href="/aa-sdk/tutorials/contract-interactions">
    Combine token gas with batched calls.
  </Card>

  <Card title="Smart sessions" icon="key-skeleton" href="/aa-sdk/tutorials/smart-sessions">
    Pair token gas with scoped sessions for high-frequency UX.
  </Card>
</CardGroup>
