Solana Pay: the underrated spec every wallet already implements
A URL scheme. That's it. Solana Pay lets you accept stablecoins or SOL via QR code or deeplink, with every wallet on the network already supporting it.
devrels.xyz/a/15Solana Pay has the strange status of being both ubiquitous and ignored. Every wallet on the network — Phantom, Backpack, Solflare, Glow, Exodus, every mobile wallet — supports it. The spec was finalised in 2022. There are hundreds of POS systems shipping with Solana Pay support today. And almost nobody writes about it.
It's the simplest possible thing: a URL scheme for payment requests. Anyone with a Solana wallet can scan a QR code, click a link, or follow a deeplink to a Solana Pay URL, and their wallet will surface a pre-filled transaction prompt. Approve, sign, done.
The transfer request URL
The simplest form. A static request to send SOL (or any SPL token) to a specific address:
solana:RECIPIENT_PUBKEY?amount=10&label=Coffee&message=Thanks&memo=order-1234
# With an SPL token (USDC):
solana:RECIPIENT_PUBKEY?amount=10&spl-token=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v&label=CoffeeThat URL, encoded as a QR code, is a complete payment request. Every Solana wallet recognises it. The user scans, sees "Send 10 USDC to Coffee", confirms, signs.
Generating the QR code
import { encodeURL, createQR } from "@solana/pay"
import { PublicKey } from "@solana/web3.js"
import BigNumber from "bignumber.js"
const url = encodeURL({
recipient: new PublicKey("9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM"),
amount: new BigNumber(10),
splToken: new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"),
reference: new PublicKey("..." /* unique ref pubkey for this order */),
label: "Coffee",
message: "Thanks for your purchase",
memo: "order-1234",
})
// QR code as an SVG
const qr = createQR(url, 320, "transparent")
const svg = await qr.getRawData("svg")The transaction request URL
The static form is fine for fixed-price items. For dynamic flows (variable amounts, server-built transactions, time-limited offers) there's the transaction request form: the URL points to your HTTPS endpoint, and the wallet POSTs back to fetch a server-built transaction.
solana:https://api.your-shop.com/pay?orderId=1234The wallet makes two HTTP calls to that endpoint:
- GET — your server returns a label and icon to show in the wallet UI.
- POST (with the user's pubkey in the body) — your server returns a base64-encoded transaction for the user to sign.
// Next.js route handler
export async function GET() {
return Response.json({ label: "Your Shop", icon: "https://your-shop.com/icon.png" })
}
export async function POST(req: Request) {
const { account } = await req.json() // the user's pubkey
const tx = new Transaction()
tx.add(
/* your instructions — e.g. token transfer + a reference key + a memo */
)
tx.feePayer = new PublicKey(account)
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash
return Response.json({
transaction: tx.serialize({ requireAllSignatures: false }).toString("base64"),
message: "Thanks for your order",
})
}Watching for payment
The reference field is the key for confirmation. Each order generates a unique reference pubkey (just a randomly generated keypair — only the address matters). Include it as a read-only account in the transaction. After payment, you poll Solana for any transaction containing that reference:
import { findReference, FindReferenceError } from "@solana/pay"
const reference = new PublicKey("...") // the unique ref for this order
try {
const sig = await findReference(connection, reference, { finality: "confirmed" })
console.log("Paid! signature:", sig.signature)
} catch (err) {
if (err instanceof FindReferenceError) {
// Not yet paid — poll again or show "waiting" UI
}
}Your server now has the signature. You verify the amount, recipient, and token mint match the expected order, then fulfill.
Why nobody talks about it
Solana Pay is small, finished, and not currently the focus of any major announcement push. There's no token, no SDK arms race, no aggressive marketing. It just works, has worked for three years, and shows up quietly in checkout flows for Shopify integrations, physical-world POS systems, in-app purchases, and tipping flows.
Which is exactly why it's the right primitive when you need to ship a payment. Wallets already understand it. Users already know how to use it. The spec hasn't changed.
References
- docs.solanapay.com — the canonical spec
- anza-xyz/solana-pay — reference implementation
- @solana/pay — the npm package
- On Solana Launch with CommerceKit (productized Pay + UI) and Kora (gasless)
If you're building anything that needs to accept Solana payments — a checkout, a paywall, a donation flow, a coffee shop register — start here. The simplest path that already works everywhere.
Keep reading
Launch lists CommerceKit as the complete e-commerce toolkit for Solana-powered stores. Under the hood it is a package graph: headless commerce logic, React UI, wallet connector, and a full Solana Pay implementation — install @solana-commerce/kit for the whole stack.
Stablecoin rails are useless at the last mile if users only have cash. MoneyGram Ramps is the Foundation’s bid to wire that last mile into SDP payments.
Spend crypto at card-accepting merchants without a manual sell step. Here is the product map, onboarding path, and what builders should take from RedotPay.
Get new articles in your inbox
Technical deep-dives on Solana tooling, infrastructure, and ecosystem. No noise.
