Transfer Requests

Create simple payment URLs for SOL and SPL token transfers

Transfer Requests

Transfer requests are the simplest form of Solana Pay integration. They create non-interactive payment URLs that wallets can use to directly compose and send transactions without requiring a server.

Overview

A transfer request URL follows this format:

solana:<recipient>
      ?amount=<amount>
      &spl-token=<spl-token>
      &reference=<reference>
      &label=<label>
      &message=<message>
      &memo=<memo>

Basic SOL Transfer

Create a simple SOL payment request:

import { createTransferRequestURL } from '@solana/pay';
import { PublicKey } from '@solana/web3.js';
import BigNumber from 'bignumber.js';

// Recipient's Solana wallet address
const recipient = new PublicKey('recipient-wallet-address');

// Amount in SOL (0.01 SOL)
const amount = new BigNumber(0.01);

// Optional: Unique reference for tracking
const reference = new PublicKey('reference-public-key');

// Create the payment URL
const url = createTransferRequestURL({
  recipient,
  amount,
  reference,
  label: 'My Store',
  message: 'Thanks for your purchase!',
});

console.log(url.toString());
// Output: solana:recipient?amount=0.01&reference=...&label=My%20Store&message=Thanks%20for%20your%20purchase!

SPL Token Transfer

Create a USDC payment request:

import { createTransferRequestURL } from '@solana/pay';
import { PublicKey } from '@solana/web3.js';
import BigNumber from 'bignumber.js';

// USDC mint address on mainnet
const usdcMint = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');

const url = createTransferRequestURL({
  recipient: new PublicKey('recipient-wallet-address'),
  amount: new BigNumber(10.50), // $10.50 USDC
  splToken: usdcMint,
  reference: new PublicKey('unique-reference-key'),
  label: 'Coffee Shop',
  message: 'Grande Latte + Tip',
});

console.log(url.toString());

URL Parameters

Required Parameters

Recipient

The recipient's Solana wallet address (base58-encoded public key):

const recipient = new PublicKey('FvJ8k8HhXp4a3zQyFMZd4FvEqcYdYE7gSZWxrEBRfBsB');

Optional Parameters

Amount

Payment amount in user-friendly units (SOL or token units):

// SOL amounts
const solAmount = new BigNumber(1.5);     // 1.5 SOL
const smallAmount = new BigNumber(0.001); // 0.001 SOL

// Token amounts (USDC example)
const usdcAmount = new BigNumber(25.99);  // $25.99 USDC

If omitted, the wallet will prompt the user to enter the amount.

SPL Token

For token transfers, specify the mint address:

// Common SPL tokens
const USDC = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
const USDT = new PublicKey('Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB');
const SOL = undefined; // Omit for native SOL transfers

Reference

Unique identifiers for tracking payments:

import { Keypair } from '@solana/web3.js';

// Generate a unique reference
const reference = Keypair.generate().publicKey;

// Or use multiple references
const references = [
  Keypair.generate().publicKey,
  Keypair.generate().publicKey,
];

Label

Merchant or app name displayed to users:

const label = 'Acme Coffee Shop';

Message

Description of the payment:

const message = 'Order #12345 - 2x Americano';

Memo

On-chain memo (publicly visible):

const memo = 'OrderId:12345';

QR Code Integration

Generate QR codes for mobile wallet scanning:

import QRCode from 'qrcode';
import { createTransferRequestURL } from '@solana/pay';

async function generatePaymentQR(paymentData) {
  const url = createTransferRequestURL(paymentData);
  
  // Generate QR code as data URL
  const qrDataURL = await QRCode.toDataURL(url.toString(), {
    width: 256,
    margin: 2,
    color: {
      dark: '#000000',
      light: '#ffffff'
    }
  });
  
  return qrDataURL;
}

// Usage
const qrCode = await generatePaymentQR({
  recipient: new PublicKey('recipient-address'),
  amount: new BigNumber(0.05),
  label: 'Donation',
  message: 'Support our project',
});

// Display in HTML
document.getElementById('qr-code').src = qrCode;

Payment Validation

Validate payments by monitoring the blockchain:

import { Connection } from '@solana/web3.js';
import { findTransactionSignature } from '@solana/pay';

async function validatePayment(reference, recipient, amount) {
  const connection = new Connection('https://api.mainnet-beta.solana.com');
  
  try {
    // Find transaction by reference
    const signature = await findTransactionSignature(
      connection,
      reference,
      { finality: 'confirmed' }
    );
    
    // Get transaction details
    const transaction = await connection.getTransaction(signature);
    
    // Validate transaction details
    if (transaction && transaction.meta?.err === null) {
      console.log('Payment confirmed:', signature);
      return { success: true, signature };
    }
  } catch (error) {
    console.log('Payment not found yet');
  }
  
  return { success: false };
}

// Monitor for payment
const reference = new PublicKey('your-reference-key');
const checkPayment = () => {
  validatePayment(reference, recipient, amount)
    .then(result => {
      if (result.success) {
        console.log('Payment received!');
        // Handle successful payment
      } else {
        // Keep checking
        setTimeout(checkPayment, 2000);
      }
    });
};

checkPayment();

Complete Example

Here's a complete example with QR code generation and payment monitoring:

import { createTransferRequestURL, findTransactionSignature } from '@solana/pay';
import { Connection, PublicKey, Keypair } from '@solana/web3.js';
import BigNumber from 'bignumber.js';
import QRCode from 'qrcode';

class SolanaPayment {
  constructor() {
    this.connection = new Connection('https://api.mainnet-beta.solana.com');
  }

  async createPayment(paymentData) {
    // Generate unique reference
    const reference = Keypair.generate().publicKey;
    
    // Create payment URL
    const url = createTransferRequestURL({
      ...paymentData,
      reference,
    });
    
    // Generate QR code
    const qrCode = await QRCode.toDataURL(url.toString());
    
    return {
      url: url.toString(),
      qrCode,
      reference,
    };
  }

  async monitorPayment(reference, onConfirmed) {
    const checkPayment = async () => {
      try {
        const signature = await findTransactionSignature(
          this.connection,
          reference,
          { finality: 'confirmed' }
        );
        
        if (signature) {
          onConfirmed(signature);
          return true;
        }
      } catch (error) {
        // Payment not found yet
      }
      
      return false;
    };

    // Check every 2 seconds
    const interval = setInterval(async () => {
      const found = await checkPayment();
      if (found) {
        clearInterval(interval);
      }
    }, 2000);
  }
}

// Usage
const payment = new SolanaPayment();

// Create payment
const { url, qrCode, reference } = await payment.createPayment({
  recipient: new PublicKey('FvJ8k8HhXp4a3zQyFMZd4FvEqcYdYE7gSZWxrEBRfBsB'),
  amount: new BigNumber(0.1),
  label: 'Demo Store',
  message: 'Test Purchase',
});

// Display QR code
document.getElementById('qr-code').src = qrCode;

// Monitor for payment
payment.monitorPayment(reference, (signature) => {
  console.log('Payment confirmed!', signature);
  // Redirect to success page or update UI
});

Best Practices

Security

  • Always validate payments server-side
  • Use unique references for each payment
  • Verify transaction amounts and recipients
  • Check for transaction success status

User Experience

  • Show clear payment amounts and descriptions
  • Provide payment status updates
  • Handle wallet connection gracefully
  • Support both QR codes and direct links

Error Handling

  • Handle wallet connection failures
  • Provide fallback for unsupported wallets
  • Show helpful error messages
  • Implement payment timeouts

Common Use Cases

Point of Sale

// Generate payment for POS terminal
const posPayment = await createTransferRequestURL({
  recipient: merchantWallet,
  amount: new BigNumber(15.99),
  label: 'Local Coffee Shop',
  message: `Receipt #${receiptNumber}`,
  memo: `POS-${terminalId}-${Date.now()}`,
});

Donations

// Flexible donation amount (user enters amount)
const donationUrl = createTransferRequestURL({
  recipient: charityWallet,
  label: 'Save the Ocean',
  message: 'Support marine conservation',
});

E-commerce Checkout

// Product purchase with order tracking
const checkoutUrl = createTransferRequestURL({
  recipient: storeWallet,
  amount: cartTotal,
  reference: orderReference,
  label: storeName,
  message: `Order ${orderId}`,
  memo: `ORDER:${orderId}`,
});