Skip to main content

Pay Gas in USDT on Solana

Send SPL token transfers from Solana accounts that hold zero SOL. Candide's Solana Paymaster, a hosted Kora endpoint, signs as the transaction fee payer and collects the fee in USDT inside the same transaction.

This guide uses @tetherto/wdk-wallet-solana-gasless, part of Tether's Wallet Development Kit, which wraps the whole flow behind a simple transfer() call.

How It Works

The paymaster speaks Kora's JSON-RPC protocol, the Solana Foundation's open fee-payer standard, so any Kora client can point at it. For each transaction:

  1. Your app builds an SPL transfer and includes a small USDT payment to the fee payer
  2. The paymaster validates the transaction against its policy, co-signs as fee payer, and submits it
  3. The fee payer spends the SOL for fees and rent; the user pays in USDT

The USDT fee is priced from the live SOL cost of the transaction. If the recipient does not yet have a USDT token account, the rent for creating it is included in the quoted fee.

Supported fee token

Mainnet USDT (Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB) is the only fee token the paymaster accepts for now.

Quickstart

You can also fork the complete example and follow along.

Step 1: Install

npm install @tetherto/wdk-wallet-solana-gasless @solana/kora

@solana/kora is only used to discover the paymaster's fee payer address at runtime.

The code in this guide was verified on Solana mainnet against @tetherto/wdk-wallet-solana-gasless@1.0.0-beta.1 and @solana/kora@0.2.1.

Step 2: Configure the Wallet

Get your Solana Paymaster URL from the Candide dashboard. The URL includes your API key, so keep it in an environment variable.

.env
# Candide's Solana Paymaster endpoint (hosted Kora). The URL includes your API key: keep it secret.
SOLANA_PAYMASTER_URL=

# Solana mainnet RPC. Use a paid provider for production.
SOLANA_NODE_URL=https://api.mainnet-beta.solana.com
index.ts
import WalletManagerSolanaGasless from '@tetherto/wdk-wallet-solana-gasless'
import { KoraClient } from '@solana/kora'

const nodeUrl = process.env.SOLANA_NODE_URL as string
const paymasterUrl = process.env.SOLANA_PAYMASTER_URL as string
const usdtMint = 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB'

// WDK wallets are seed-phrase based: the account is derived from a
// BIP-39 mnemonic at m/44'/501'/0'/0'
const seedPhrase = process.env.SEED_PHRASE || WalletManagerSolanaGasless.getRandomSeedPhrase()

// The paymaster reports its fee payer address; transactions must name it
const kora = new KoraClient({ rpcUrl: paymasterUrl })
const { signer_address: paymasterAddress } = await kora.getPayerSigner()

const wallet = new WalletManagerSolanaGasless(seedPhrase, {
provider: nodeUrl,
paymasterUrl,
paymasterAddress,
paymasterToken: { address: usdtMint },
transferMaxFee: 1_000000n, // abort if the quoted fee exceeds 1 USDT
})

const account = await wallet.getAccount(0)
console.log(`Solana account: ${await account.getAddress()}`)

Step 3: Quote the Fee

quoteTransfer prices the transfer without submitting it. The fee is returned in USDT base units (6 decimals).

const quote = await account.quoteTransfer({
token: usdtMint,
recipient,
amount: 100000n, // 0.1 USDT
})
console.log(`Estimated network fee: ${Number(quote.fee) / 1e6} USDT`)

Step 4: Send the Transfer

transfer() builds the transaction, gets it co-signed by the fee payer, and submits it. The account pays the fee from its USDT balance and never touches SOL.

const result = await account.transfer({
token: usdtMint,
recipient,
amount: 100000n,
})
console.log(`Transaction signature: ${result.hash}`)
console.log(`Fee paid: ${Number(result.fee) / 1e6} USDT`)

transfer() returns as soon as the transaction is submitted. Poll getSignatureStatuses on your RPC if you need to wait for confirmation, as the complete example below does.

Complete Runnable Example

The full example lives in the WDK + Candide examples repository. It checks the account's USDT balance, quotes the fee, sends the transfer, and waits for on-chain confirmation:

git clone https://github.com/candidelabs/tether-wdk-candide.git
cd tether-wdk-candide && npm install
cp .env.example .env # fill in SOLANA_PAYMASTER_URL
npm run solana-transfer-usdt-gas
solana-gasless/01-usdt-gas/index.ts
loading...
.env
# Candide's Solana Paymaster endpoint (hosted Kora). The URL includes your API key: keep it secret.
SOLANA_PAYMASTER_URL=

# Solana mainnet RPC. Use a paid provider for production.
SOLANA_NODE_URL=https://api.mainnet-beta.solana.com

# BIP-39 seed phrase. If not set, the script generates one and prints it.
SEED_PHRASE=

# Optional: transfer recipient. Defaults to the account's own address.
# SOLANA_RECIPIENT=

# Optional: amount in USDT base units (6 decimals). Defaults to 100000 (0.1 USDT).
# SOLANA_TRANSFER_AMOUNT=

Good to Know

  • Fee guardrail: transferMaxFee aborts any transfer whose quoted fee exceeds the limit, protecting users from fee spikes.
  • New recipients cost more: the first transfer to an address without a USDT token account includes the account's rent in the fee. Subsequent transfers cost a fraction of a cent.
  • Token program support: the paymaster accepts the legacy SPL Token program; Token-2022 mints are not supported yet.
  • No SOL ever needed: the account never holds SOL, not for fees and not for rent.