---
title: How Payments Work on Solana
description: Core concepts for sending and receiving payments 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 Model    | Solana                  | Description                                 |
| ---------------------------- | ----------------------- | ------------------------------------------- |
| Customer ID / Account number | Wallet address          | Unique identifier for an account holder     |
| Currency (USD, EUR)          | Token Mint (USDG, USDC) | The asset type being transferred            |
| Balance by currency          | Token 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:

| Token | Issuer | Mint Address                                   |
| ----- | ------ | ---------------------------------------------- |
| USDC  | Circle | `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` |
| USDT  | Tether | `Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB` |
| PYUSD | PayPal | `2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo` |
| USDG  | Paxos  | `2u1tszSeqZ3qBWF3uNGPFc8TzMk2tdiwknnRMWGWjGWH` |

For more information on stablecoins on Solana, see the
[Stablecoins](https://solana.com/solutions/stablecoins) solution page.

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

## 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 Accounts](/assets/docs/payments/payment-flow.png)

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.

```typescript
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 onchain code that verifies
transfers and updates balances atomically.

Solana has two token programs:

| Program           | Address                                       | Example Tokens that <br/> use this program |
| ----------------- | --------------------------------------------- | ------------------------------------------ |
| **Token Program** | `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA` | USDC, USDT                                 |
| **Token-2022**    | `TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb` | PYUSD, 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**:

```typescript
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
});
```

<Callout type="caution">
  Deriving an ATA with the wrong program will produce an invalid address. Always
  match the program to the token's mint.
</Callout>

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

```typescript
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:

```typescript
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:

```typescript
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](/docs/tokens/extensions) documentation.

## Fees

Solana payments involve up to three cost components:

| Fee Type                    | SOL                         | USD (est.) | When                                                     |
| --------------------------- | --------------------------- | ---------- | -------------------------------------------------------- |
| **Base transaction fee**    | 5,000 lamports<sup>\*</sup> | ~$0.0007   | Every transaction (bundle multiple payments to amortize) |
| **Priority fee**            | Variable                    | Variable   | Optional; faster inclusion during congestion             |
| **Account creation (rent)** | ~0.0029 SOL                 | ~$0.40     | Only 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.

<Callout type="info">
  Solana uses [local fee
  markets](https://github.com/solana-foundation/solana-improvement-documents/pull/436)—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](https://github.com/solana-foundation/solana-improvement-documents/pull/436).
</Callout>

<Callout>
  You can abstract fees entirely so users never interact with SOL. See [Fee
  Abstraction](/docs/payments/send-payments/payment-processing/fee-abstraction)
  for implementation patterns.
</Callout>

## 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](/docs/payments/send-payments/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

<Cards>
  <Card
    title="Interacting with Solana"
    href="/docs/payments/interacting-with-solana"
  >
    Connect to the network and explore Solana payment data.
  </Card>
  <Card title="Send your first payment" href="/docs/payments/send-payments">
    Build and send a stablecoin transfer.
  </Card>
</Cards>

<br />
<small>
  <sup>\*</sup> a lamport is the smallest unit of SOL and is equal to
  0.000000001 SOL
</small>
