Written by
Owen Venter
Published on
August 29, 2023
Copy link

Solana Pay: All You Need To Know

Built on the Solana blockchain, Solana Pay is a common language to simplify transactions within URLs. Offering instant transactions and fees that are fractions of a penny, it's a new way to make payments smoother and enhance the experience for users.

Think of these URLs as special website addresses that can be turned into QR codes or NFC tags, or shared between people and apps when a payment needs to be made or a transaction set up. This makes the whole process of using Solana transactions more seamless and secure.

Solana Pay: How It Works

Solana Pay presents a different way for users to interact with smart contracts and transfer tokens. Instead of needing to connect a wallet they can view and sign a transaction via a link. This removes the barrier of needed to connect your wallet to interact. This can be for both simple payments and interactions with apps, thus being able to handle both transaction requests and transfer requests.

The primary feature of Solana Pay is the Transfer Request, a non-interactive request for a transfer of SOL or SPL Tokens. It is important to note that not all Solana wallets accept this format, however majority of the popular wallets do (Phantom, Solflare and Glow). Here's what a Solana Pay transfer request URL looks like:


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

The parameters in the URL are used by a wallet to directly compose a transaction. In this guide we will be focusing on Transfer Requests but its important to note that almost any Solana transaction can be converted into a valid Solana Pay URL.

Parameters

Let’s now breakdown each of the parameters:

Recipient: This is the public key of the person you're sending SOL to. For SPL Token transfers, you specify the token type in the spl-token field. The wallet uses this info to figure out the actual account for the transfer.

Amount: The number of SOL or tokens you're sending. If it's less than 1, it should have a 0 before the decimal point. If there's no amount, the wallet will ask you to input it. If transferring SOL then this amount is the amount in SOL not in lamports.

SPL Token: This optional field represents the mint address for a certain type of SPL Token account. If you don't include it the transaction will just be a normal SOL transfer.

Reference: A unique identifier encoded in the transaction. This is used to help find specific transactions and verify transaction results.

Label: A short description of where the transfer request is coming from. This could be a store or an app. Wallets display this to the user to provide information on the transaction.

Message: An encoded URL that stores details about why the transfer is happening. This could include what you're buying, an order ID, or a simple note. This is also used by wallets to provide more context to the user.

Memo: A note included in the payment transaction. This should not include any private or sensitive info as this is stored on-chain.

These details give you full control over your transaction, making the interaction between your wallet and the recipient straightforward. For security, apps should only process the transaction when it’s been confirmed on-chain.

Solana Pay: Example URLs

Sending 0.5 SOL:


solana:CckxW6C1CjsxYcXSiDbk7NYfPLhfqAm3kSB5LEZunnSE?amount=0.5&label=Amazon&message=Thanks%20for%20shopping%20at%20Amazon&memo=ID321

Sending 5 USDC:


solana:CckxW6C1CjsxYcXSiDbk7NYfPLhfqAm3kSB5LEZunnSE?amount=5&spl-token=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v

Solana Pay: Next.js Implementation

What will we be building?At the end of this tutorial you should have a web app that creates a Solana Pay QR code from data that users input (a wallet address and an amount of SOL). This app will also show the status of the payment by performing checks of the transaction status on the blockchain.

Prerequisites

  • You need to have Node.js installed on your machine
  • Have a good understanding of React and TypeScript.
  • You will need a Solana wallet to send and receive funds.
  • You will also need a Helius API key for any onchain work. You can get a Helius API key for free with just a wallet on our dev portal.

Steps

  1. Create a Next.js App

First make sure that Node is installed then create the Next.js app. In the terminal type in the following:


npx create-next-app@latest sol-pay-app


You will then be prompted with a few questions, you can select these options:

Next.js setup settings

Once this has been created you can navigate into the directory with:


cd sol-pay-app

Once inside the directory you can start the app up with:


npm run dev

You can head to http://localhost:3000/ to view your app.

  1. This app will require two packages: Solana web3.js and Solana Pay. We will also be using a package called “react-qr-code” to generate QR code versions of the transaction links. To install these packages you can run:

npm install @solana/pay @solana/web3.js bignumber.js react-qr-code --save
  1. Create a front end to display the Solana Pay QR code and allow edits to the address that will receive the SOL and the amount of SOL. For safety reasons it is recommended that you perform all the logic on the backend (especially setting the amounts and recipient). However, to keep things simple we will also be performing the logic of the app on the front end.

a. Navigate to the pages directory, and open the index.tsx file. This file represents your home page. Replace the existing code with the following:


export default function Home() {
  return (
    <div className="flex flex-col items-center justify-center min-h-screen bg-gray-100">
      <h1 className="mb-6 text-3xl font-bold text-orange-700">
        Helius Solana Pay Demo
      </h1>
    </div>
  );
}

b. Add the imports at the top of the page.


// imports
import {Cluster,clusterApiUrl,Connection,PublicKey,Keypair} from "@solana/web3.js";
import { encodeURL, createQR,findReference, FindReferenceError, validateTransfer } from "@solana/pay";
import BigNumber from "bignumber.js";
import { useState } from "react";
import QRCode from "react-qr-code";

c. Create a variable to store your Helius RPC URL:
(If you plan to deploy this app you should make use of an ENV file or use a proxy to hide your API key)


// get a free Helius RPC at helius.dev
 const RPC="https://rpc.helius.xyz/?api-key="!
 

d. Create a Solana Connection object with your Helius RPC URL:


console.log('Connecting to the Solana network\n');
const connection = new Connection(RPC, 'confirmed');

e. Next we will declare and initialise the variables that we will be using. This will include all the variables required in the Solana Pay URL. You can create these variables inside the Home() function.


// URL Variables
  const [address, setAddress] = useState("");
  const [recipient, setRecipient] = useState(
    new PublicKey("CckxW6C1CjsxYcXSiDbk7NYfPLhfqAm3kSB5LEZunnSE"));
  const [amount, setAmount] = useState(new BigNumber(1));
  const [message, setMessage] = useState("Helius Demo Order");
  const reference = new Keypair().publicKey;
  const label = "Helius Super Store";
  const memo = "Helius#4098";

  // for the QR code
  const [qrCodeValue, setQrCodeValue] = useState('');
  const [paymentStatus, setPaymentStatus] = useState('');
  

f. Next we will need to write a function to create the payment. This function will take the variables from the input fields and create an encoded Solana Pay URL with the encodedURL method. Once this has been done we will convert the URL object to a string and save this to be used later to display the QR code. Add the following code underneath the variables:


async function createPayment() {
    console.log("Creating a payment URL \n");
    setRecipient(new PublicKey(address));
    const url = encodeURL({
      recipient,
      amount,
      reference,
      label,
      message,
      memo,
    });

    setQrCodeValue(url.toString()); // convert URL object to string
    checkPayment();
  }

g. Once the payment has been created we will present the QR code to the user and the user will be able to scan the code and perform the transaction. For us to be able to verify if that transaction has happened and was successful we need to create a function to check the on-chain payment transaction. In the createPayment function above you can see that we call this function right after creating the transaction.


async function checkPayment() {
    // update payment status
    setPaymentStatus('pending');

		// search for transaction
	
		// validate transaction
}

This function will contain two parts: Searching for the confirmed transaction and validating the transaction.Since the user will be interacting with the transaction from a separate device there is no way to know exactly when the transaction has happened. Instead we will repeatedly check to see if a transaction with the matching reference has happened and what its status is. This is done with Solana Pay’s findReference method.


// search for transaction
console.log('Searching for the payment\n');
    let signatureInfo;
   
    const {signature} = await new Promise((resolve, reject) => {
       
        const interval = setInterval(async () => {
            console.count('Checking for transaction...'+reference);
            try {
                signatureInfo = await findReference(connection, reference, { finality: 'confirmed' });
                console.log('\n Signature: ', signatureInfo.signature,signatureInfo);
                clearInterval(interval);
                resolve(signatureInfo);
            } catch (error: any) {
                if (!(error instanceof FindReferenceError)) {
                    console.error(error);
                    clearInterval(interval);
                    reject(error);
                }
            }
        }, 250);
    });
	// Update payment status
  setPaymentStatus('confirmed');

Once the transaction has been found we need to ensure that the correct things happened in the transaction. This includes checking that the correct recipient received the tokens and that the correct amount of tokens were sent. To do this we will use Solana Pay’s validateTransfer method:


//validate transaction
    console.log('Validating the payment\n');
    try {
      await validateTransfer(connection, signature, { recipient: recipient, amount });

      // Update payment status
      setPaymentStatus('validated');
      console.log('Payment validated');
      return true;
      
  } catch (error) {
      console.error('Payment failed', error);
      return false;
  }

h. The final step in the process will be adding the TSX to set up the UI. We will be using Tailwind to style the elements. The UI will include a heading, two input fields for the recipient’s address and amount of SOL, a button and a Solana Pay QR code. The Solana Pay QR code will be replaced with text saying “Payment Validated” once the payment has been validated. The button will call the createPayment method.


//validate transaction
return (
    <div className="flex flex-col items-center justify-center min-h-screen bg-gray-100">
      <h1 className="mb-6 text-3xl font-bold text-orange-700">
        Helius Solana Pay Demo
      </h1>
      <div className="w-full max-w-md p-6 mx-auto bg-white rounded-xl shadow-md">
        <div className="mb-4">
          <label className="block mb-2 text-sm font-medium text-gray-700">
            Address:
          </label>
          <input
            type="text"
            onChange={(e) => setAddress(e.target.value)}
            className="w-full px-3 py-2 text-sm leading-tight text-gray-700 border rounded shadow appearance-none focus:outline-none focus:shadow-outline"
          />
        </div>
        <div className="mb-4">
          <label className="block mb-2 text-sm font-medium text-gray-700">
            Amount:
          </label>
          <input
            type="number"
            onChange={(e) => setAmount(new BigNumber(e.target.value))}
            className="w-full px-3 py-2 text-sm leading-tight text-gray-700 border rounded shadow appearance-none focus:outline-none focus:shadow-outline"
          />
        </div>
        <div className="flex justify-center items-center">
        <button 
          className="px-4 py-2 font-bold text-white bg-blue-500 rounded hover:bg-blue-700" 
          onClick={createPayment}
        >
          Create QR Code
        </button>
        </div>
        
        <div>
        {paymentStatus === 'validated' ? <p className="mt-4 text-green-500 text-center">Payment Validated</p> : <div className="flex justify-center mt-4">
          {qrCodeValue && }
        </div>}
      </div>
      </div>
      
    </div>
  );

Once you have completed all these steps you should have an app that looks and behaves like this:

Solana Pay Demo

Solana Pay: Creating Seamless Experiences

Whether you are making a payment or setting up a transaction, Solana Pay's encoding system offers a seamless experience for users and apps alike. I hope this guide serves as a comprehensive tool for understanding and implementing Solana Pay, making your interactions with the Solana blockchain smoother and more efficient. If you require any assistance please feel free to join the Helius Discord or reach out on Twitter.

You can find the completed code that should be in the index.tsx file here: https://github.com/owenventer/SolanaPayDemo/blob/main/src/pages/index.tsx

You can read more about Solana Pay here: https://solanapay.com/

You can find the Solana Pay documentation here: https://docs.solanapay.com/