> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://www.openfort.io/api/mcp` to find what you need.
>
> **Have feedback?** Use `submit_feedback` on the same MCP server.

# Pay Solana gas in stablecoins (USDC, USDT, USDG)

On Solana, you can let users pay transaction fees directly in stablecoins such as **USDC, USDT, or USDG** instead of SOL. Each is an SPL token, so the same flow applies to any SPL mint your project accepts. The Openfort paymaster (Kora) signs the transaction as fee payer, and the user pays the fee on-chain in an SPL mint the paymaster accepts — your project balance is never debited.

In the dashboard, this is the **User pays** sponsorship mode with SPL tokens selected; the gas sponsorship's details show it as **Charge SPL tokens (Solana)**.

## How it works

1. You create a gas sponsorship with `splTokens` set to the SPL mints your project accepts.
2. The end-user's transaction must include an SPL token transfer paying Kora in a mint Kora accepts (your wallet/SDK constructs this).
3. The client calls [`signTransaction`](https://www.openfort.io/docs/products/infrastructure/paymaster/solana/endpoints#signtransaction) or [`signAndSendTransaction`](https://www.openfort.io/docs/products/infrastructure/paymaster/solana/endpoints#signandsendtransaction).
4. Openfort checks the transaction against your policy rules and routes it to the SPL paymaster. Kora then checks that it contains a fee transfer in a mint from its supported list; otherwise the request is rejected.
5. Kora signs as fee payer and (optionally) broadcasts the transaction. The user pays the SPL fee on-chain; the project is not billed.

## Discover the accepted SPL tokens

Kora is the source of truth for which mints can be used for fee payment. Query it for the active list — only mints from this list are accepted for fee payment:

```bash
# devnet — use `mainnet-beta` for live mode
curl -X POST https://api.openfort.io/rpc/solana/devnet \
  -H "Authorization: Bearer $YOUR_PUBLISHABLE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "getSupportedTokens",
    "params": [],
    "id": 1
  }'
```

Response:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tokens": [
      "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
    ]
  }
}
```

See the [`getSupportedTokens`](https://www.openfort.io/docs/products/infrastructure/paymaster/solana/endpoints#getsupportedtokens) endpoint reference for details.

### Common stablecoin SPL mints

These are the mainnet mint addresses for the stablecoins most projects accept as fee tokens. A mint is only usable for fees once it appears in `getSupportedTokens` for your environment — always confirm the active list before creating a policy.

| Network | Token | Mint address | Token program |
|---------|-------|--------------|---------------|
| **Solana mainnet-beta** | USDC | `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` | SPL Token |
| **Solana mainnet-beta** | USDT | `Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB` | SPL Token |
| **Solana mainnet-beta** | USDG | `2u1tszSeqZ3qBWF3uNGPFc8TzMk2tdiwknnRMWGWjGWH` | Token-2022 |
| **Solana devnet** | USDC | `4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU` | SPL Token |

:::info
USDG uses the [Token-2022](https://solana.com/docs/tokens) program rather than the classic SPL Token program. Confirm it is returned by `getSupportedTokens` before adding it to a policy.
:::

## Create the gas sponsorship

::::steps
### Create a policy for the sponsorship rules

Create a [policy](https://www.openfort.io/docs/configuration/policies) with the `sponsorSolTransaction` operation. Add Solana criteria (`solAddress`, `programId`, `mintAddress`, …) to restrict which transactions are eligible, or leave `criteria` empty to accept every Solana transaction:

```bash
curl -X POST https://api.openfort.io/v2/policies \
  -H "Authorization: Bearer $YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "scope": "project",
    "description": "Sponsor Solana transactions paid in SPL",
    "rules": [
      {
        "action": "accept",
        "operation": "sponsorSolTransaction",
        "criteria": []
      }
    ]
  }'
```

### Create the gas sponsorship with SPL tokens

Link the policy to a gas sponsorship and pass the accepted SPL mints in `strategy.splTokens`:

```bash
curl -X POST https://api.openfort.io/v2/fee-sponsorship \
  -H "Authorization: Bearer $YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Pay gas in USDC on Solana",
    "strategy": {
      "sponsorSchema": "charge_custom_tokens",
      "splTokens": [
        "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
      ]
    },
    "policyId": "ply_..."
  }'
```

The response echoes the accepted mints in `strategy.splTokens`:

```json
{
  "id": "pol_...",
  "object": "feeSponsorship",
  "enabled": true,
  "strategy": {
    "sponsorSchema": "charge_custom_tokens",
    "splTokens": [
      "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
    ]
  },
  "policyId": "ply_..."
}
```

:::tip
You can list multiple mints in `splTokens` to give end-users a choice of fee tokens. The user's transaction picks the actual fee mint by including an SPL transfer to Kora in one of them. For `signTransaction` and `signAndSendTransaction`, the fee mint is checked against Kora's supported list (`getSupportedTokens`), not against this policy's `splTokens`, so keep the two aligned.
:::
::::

## Construct the user transaction

Your client builds the user's transaction with two things:

* The business instructions (transfer, swap, …) with the user as signer.
* An SPL [token transfer](https://spl.solana.com/token#transferring-tokens) paying the Kora fee payer in one of the accepted mints. The amount should cover Kora's fee — use [`estimateTransactionFee`](https://www.openfort.io/docs/products/infrastructure/paymaster/solana/endpoints#estimatetransactionfee) with `fee_token` set to the mint to get a quote.

Both `TransferChecked` (token program instruction `12`) and the legacy `Transfer` (instruction `3`) are recognised, under either the SPL Token or the Token-2022 program. `TransferChecked` carries the mint inline; for a legacy `Transfer` the paymaster reads the mint off the token account instead. Prefer `TransferChecked` — it fails on-chain if the mint or decimals don't match, which a legacy `Transfer` cannot detect.

Then call [`signTransaction`](https://www.openfort.io/docs/products/infrastructure/paymaster/solana/endpoints#signtransaction) or [`signAndSendTransaction`](https://www.openfort.io/docs/products/infrastructure/paymaster/solana/endpoints#signandsendtransaction). If the policy enforces a specific contract scope, pass `policyId`:

```bash
curl -X POST https://api.openfort.io/rpc/solana/mainnet-beta \
  -H "Authorization: Bearer $YOUR_PUBLISHABLE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "signAndSendTransaction",
    "params": {
      "transaction": "<base64-encoded transaction>",
      "policyId": "pol_..."
    },
    "id": 1
  }'
```

If the transaction doesn't include a fee transfer to Kora in a mint Kora supports, Kora rejects the request.

## API reference

### Create gas sponsorship

`POST /v2/fee-sponsorship`

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | No | Display name |
| `strategy.sponsorSchema` | string | Yes | Must be `charge_custom_tokens` |
| `strategy.splTokens` | string\[] | Yes | SPL mint addresses (base58, 32–44 chars, up to 50) accepted as fee payment. Duplicates are rejected. Use mints from `getSupportedTokens` — the API doesn't check this, Kora does at request time |
| `policyId` | string | Yes | Policy ID (`ply_...`) with a `sponsorSolTransaction` rule |

:::warning
`splTokens` is mutually exclusive with `tokenContracts`, `tokenContract`, and `tokenContractAmount`. Mixing them returns `400 Bad Request`. `tokenContractAmount` is also rejected on Solana SPL policies because Kora computes the exchange rate at request time.
:::
