How Payments Work on Solana

Before building payment flows on Solana, you'll need to understand five core concepts: wallets, stablecoins, token accounts, fees, and transactions. Solana payments map cleanly to multi-currency payment systems:

Traditional Payment ModelSolanaDescription
Customer ID / Account numberWallet addressUnique identifier for an account holder
Currency (USD, EUR)Token Mint (USDG, USDC)The asset type being transferred
Balance by currencyToken Account (ATA)Holds a balance of a specific currency/mint

Just as a bank customer has a single identity but holds separate balances for each currency, a Solana wallet has one address but a distinct token account for each asset it holds. Let's break down each component.

Wallets: Senders and Receivers

Every payment involves two parties, each identified by a wallet address—a unique 32-byte public key (e.g., 7EcDhS...).

  • Sender: The wallet initiating the payment. Must hold sufficient stablecoin account balance and sign the transaction.
  • Receiver: The destination wallet. Does not need to sign or hold an existing balance.
  • Fee Payer: The optional fee payer wallet. Can be used to subsidize or enable stablecoin-only transactions between users.

Think of wallet addresses like bank account numbers: public, safe to share, and required to send or receive funds.

Stablecoins

Stablecoins are referred to as "tokens" on Solana. Tokens represent an asset type on the network. Each token has a unique identifier called a "mint address". When building payment systems, you'll reference these mint addresses to identify the asset you are interacting with. Here are some common stablecoin mints on mainnet:

TokenIssuerMint Address
USDCCircleEPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
USDTTetherEs9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB
PYUSDPayPal2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo
USDGPaxos2u1tszSeqZ3qBWF3uNGPFc8TzMk2tdiwknnRMWGWjGWH

For more information on stablecoins on Solana, see the Stablecoins solution page.

When accepting payments, always validate the mint address and token program. Tokens can share names but have different issuers and underlying assets.

Token Accounts

Wallets don't hold tokens directly. Instead, each wallet has authority over a token account for each type of token it holds. Payments are made by transferring tokens from a sender's token account to a receiver's token account of the same mint:

Token AccountsToken Accounts

An Associated Token Account is a deterministic token account tied to a specific wallet and mint. Given a wallet address and mint, the ATA address is always the same.

  1. One ATA per mint. A wallet has exactly one ATA for USDC, one for USDT, etc.
  2. Must exist before receiving. You cannot send tokens to an ATA that doesn't exist.
  3. Sender typically creates. If the receiver's ATA doesn't exist, the sender can create it as part of the payment transaction.
import { findAssociatedTokenPda } from "@solana-program/token";
const [receiverATA] = await findAssociatedTokenPda({
mint: USDG_MINT_ADDRESS,
owner: receiverWallet.address,
tokenProgram: TOKEN_PROGRAM_ADDRESS
});

Token Programs

On Solana, programs are executable logic that govern account state. Token accounts are managed by a Token Program—the on-chain code that verifies transfers and updates balances atomically.

Solana has two token programs:

ProgramAddressExample Tokens that
use this program
Token ProgramTokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DAUSDC, USDT
Token-2022TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEbPYUSD, USDG

Token-2022 (also called "Token Extensions") adds features like transfer hooks, transfer fees, and confidential transfers. Both programs work similarly for basic transfers, but you must use the correct one when deriving ATAs.

Why This Matters

The Token Program used to create a token governs the instructions and account state for the token. If you use the wrong program, you will not be able to transfer the token.

ATAs are derived from three inputs: wallet + mint + token_program. Using the wrong program produces an entirely different address:

import {
findAssociatedTokenPda,
TOKEN_PROGRAM_ADDRESS
} from "@solana-program/token";
import { TOKEN_2022_PROGRAM_ADDRESS } from "@solana-program/token-2022";
// USDC uses Token Program
const [usdcAta] = await findAssociatedTokenPda({
mint: USDC_MINT,
owner: walletAddress,
tokenProgram: TOKEN_PROGRAM_ADDRESS // ✓ Correct
});
// ❌ This will produce a different address because it uses the wrong program
const [wrongUsdcAta] = await findAssociatedTokenPda({
mint: USDC_MINT,
owner: walletAddress,
tokenProgram: TOKEN_2022_PROGRAM_ADDRESS // ❌ Wrong program
});
// PYUSD uses Token-2022
const [pyusdAta] = await findAssociatedTokenPda({
mint: PYUSD_MINT,
owner: walletAddress,
tokenProgram: TOKEN_2022_PROGRAM_ADDRESS // ✓ Correct
});

Deriving an ATA with the wrong program will produce an invalid address. Always match the program to the token's mint.

The same principle applies to transfer instructions. Each token program has its own transfer instruction, and you must invoke the correct one:

import { getTransferInstruction } from "@solana-program/token";
import { getTransferInstruction as getTransferInstruction22 } from "@solana-program/token-2022";
// For USDC (Token Program)
const usdcTransferIx = getTransferInstruction({
source: senderUsdcAta,
destination: receiverUsdcAta,
authority: senderWallet,
amount: 1_000_000n // 1 USDC (6 decimals)
});
// For PYUSD (Token-2022)
const pyusdTransferIx = getTransferInstruction22({
source: senderPyusdAta,
destination: receiverPyusdAta,
authority: senderWallet,
amount: 1_000_000n // 1 PYUSD (6 decimals)
});
// *Note*: Most token program JS Client functions include the ability
// to specify the token program address. Generally, defining it is a
// good practice to ensure you are fully aware of the program you are using
const usdcTransferIx2 = getTransferInstruction(
{
source: senderUsdcAta,
destination: receiverUsdcAta,
authority: senderWallet,
amount: 1_000_000n // 1 USDC (6 decimals)
},
{ tokenProgram: TOKEN_PROGRAM_ADDRESS }
);

Sending a transfer instruction to the wrong program will fail. The program validates that it owns the token accounts involved—accounts created by Token Program cannot be transferred via Token-2022, and vice versa.

To verify which program a token or token account uses, fetch the mint or token account and check its owner field:

import { createSolanaRpc, address } from "@solana/kit";
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
const accountInfo = await rpc.getAccountInfo(address(mintAddress)).send();
// The owner field tells you which program manages this token
const tokenProgram = accountInfo.value?.owner;
// Returns: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA (Token Program)
// or: TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb (Token-2022)

For payment applications, store the correct program address alongside each supported token:

const SUPPORTED_TOKENS = {
USDC: {
mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
program: TOKEN_PROGRAM_ADDRESS,
decimals: 6
},
PYUSD: {
mint: "2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo",
program: TOKEN_2022_PROGRAM_ADDRESS,
decimals: 6
}
};

Token Extensions

The Token Extensions Program (Token 2022) provides more features through extra instructions referred to as extensions. Extensions are optional features you can add to a token mint or token account.

For more information on Token Extensions, see the Token Extensions documentation.

Fees

Solana payments involve up to three cost components:

Fee TypeSOLUSD (est.)When
Base transaction fee5,000 lamports*~$0.0007Every transaction (bundle multiple payments to amortize)
Priority feeVariableVariableOptional; faster inclusion during congestion
Account creation (rent)~0.0029 SOL~$0.40Only when creating a new token account

Total cost per payment: Under $0.001 for most transfers. If creating a new token account, expect ~$0.40 total.

Solana uses local fee markets—each program's transactions compete only with other transactions targeting the same state. This means payment fees stay low and predictable even during periods of high network activity elsewhere. Rent costs are also planned to decrease 50% in the near future.

You can abstract fees entirely so users never interact with SOL. See Fee Abstraction for implementation patterns.

Transactions and Instructions

A transaction is the atomic unit of execution on Solana—either every operation succeeds, or none do. Each transaction contains one or more instructions, which are individual commands (e.g., "transfer 10 USDC," "create token account").

A typical payment transaction might include two instructions: create the receiver's token account (if needed), then transfer tokens. Both execute atomically—no partial states. As you will see in Payment Processing, you can bundle multiple payments into a single transaction to reduce costs and increase throughput.

Putting It Together

A typical payment flow:

  1. Gather input. Get sender and receiver wallet addresses and the mint address of the token being transferred.
  2. Derive ATAs. Determine the token accounts for both parties.
  3. Build and sign. Construct the transaction with necessary transfer instructions, sign with the sender's key.
  4. Send & Confirm. Transaction settles in under a second.

Next Steps


* a lamport is the smallest unit of SOL and is equal to 0.000000001 SOL

Is this page helpful?

Table of Contents

Edit Page

Managed by

© 2026 Solana Foundation.
All rights reserved.
Get connected