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 USDCIf 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 transfersReference
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}`,
});