How to Launch a Solana Presale Web App Without a Smart Contract Step-by-Step Guide For My Adaa.
This article guides you through creating a Solana presale web app using Next.js, React.js, and several essential packages. This tutorial assumes you have a basic understanding of Next.js, React.js, and JavaScript. The packages we’ll use are:
Next.js
Solana Wallet Adapter
Solana Token Package
Solana Web3 Package
TailwindCSS for styling
QuickNode account
Prerequisites
Ensure you have Node.js installed on your machine. Then, follow the steps below to set up your development environment.
Step 1: Create a Next.js App
First, create a new Next.js app with TailwindCSS:
npx create-next-app@latest
# Select yes for TailwindCSS
Step 2: Install Necessary Packages
Navigate to your project directory and install the required packages:
npm install @solana/wallet-adapter-react @solana/wallet-adapter-wallets @solana/wallet-adapter-react-ui @solana/web3.js @solana/spl-token tailwindcss
Step 3: Set Up QuickNode
Create an account on QuickNode and get your RPC endpoint for the Solana devnet. Set this as your endpoint in your code.
Step 4: Project Structure and Basic Setup
Clear the default Next.js content and create a providers file in the app
directory.
Create a Wallet Layout Component
Create a new file WalletLayout.tsx
in the app
directory and add the following code:
"use client";
import * as web3 from '@solana/web3.js';
import * as walletAdapterReact from '@solana/wallet-adapter-react';
import * as walletAdapterWallets from '@solana/wallet-adapter-wallets';
import { WalletModalProvider, WalletMultiButton } from '@solana/wallet-adapter-react-ui';
import '@solana/wallet-adapter-react-ui/styles.css';
import { useConnection } from '@solana/wallet-adapter-react';
import { PropsWithChildren, useState } from 'react';
const QUICKNODE_RPC = 'YOUR_QUICKNODE_RPC_ENDPOINT';
const WalletLayout = ({ children }: PropsWithChildren) => {
const [balance, setBalance] = useState<number | null>(0);
const endpoint = QUICKNODE_RPC;
const wallets = [
new walletAdapterWallets.PhantomWalletAdapter(),
];
return (
<walletAdapterReact.ConnectionProvider endpoint={endpoint}>
<walletAdapterReact.WalletProvider wallets={wallets}>
<WalletModalProvider>
{children}
</WalletModalProvider>
</walletAdapterReact.WalletProvider>
</walletAdapterReact.ConnectionProvider>
);
};
export default WalletLayout;
Import this WalletLayout
in your layout file and wrap it around your entire app to allow users to connect and transact with your presale.
This layout component sets up the Solana connection and wallet provider for the entire application, making it easy to access the wallet functionality across different components.
Step 5: Create the Presale UI
Create a simple UI to display the token price per SOL, an input for the amount of tokens to buy, and a button to send the transaction.
// pages/index.tsx
import { useConnection, useWallet } from '@solana/wallet-adapter-react';
import { useState } from 'react';
import * as web3 from '@solana/web3.js';
import WalletLayout from '../components/WalletLayout';
import { WalletMultiButton } from '@solana/wallet-adapter-react-ui'
const TOKEN_ADDRESS = 'YOUR_TOKEN_ADDRESS'; // Change this to your token address
const Home = () => {
const { connection } = useConnection();
const [amount, setAmount] = useState(0);
const [txSig, setTxSig] = useState(null)
return (
<WalletLayout>
<div className="container mx-auto">
<WalletMultiButton
className='!border-brand-cyan w-full !rounded-[20px] bg hover:!bg-[#161b19] transition-all duration-200'
/>
<h1 className="text-4xl font-bold">Solana Token Presale</h1>
<div className="mt-8">
<p>Token Price: 1 SOL</p>
<input
type="number"
className="mt-4 p-2 border"
placeholder="Amount of tokens"
onChange={(e) => setAmount(parseInt(e.target.value))}
/>
<button
className="mt-4 p-2 bg-blue-500 text-white"
>
Buy Tokens
</button>
</div>
{txSig && <p>Transaction Signature: {txSig}</p>}
</div>
</WalletLayout>
);
};
export default Home;
This file sets up the main UI for the presale page, including a connect wallet button using the Solana Wallet Adapter, an input for the number of tokens to buy, and a button to initiate the transaction.
Step 6: Create the useSendTokens
Hook
Create a new file useSendTokens.tsx
:
import { useState } from "react";
import { Keypair, PublicKey, sendAndConfirmTransaction, Transaction, TransactionExpiredBlockheightExceededError } from "@solana/web3.js";
import { getOrCreateAssociatedTokenAccount, createTransferInstruction } from "@solana/spl-token";
import { useConnection } from "@solana/wallet-adapter-react";
const MINT_ADDRESS = "YOUR_TOKEN_MINT_ADDRESS"; // Change this to your token address
const secret = [
// Your secret key array
];
const FROM_KEYPAIR = Keypair.fromSecretKey(new Uint8Array(secret));
const useSendTokens = () => {
const { connection } = useConnection();
const [isOpen, setIsOpen] = useState(false);
const [transactionUrl, setTransactionUrl] = useState("");
const [success, setSuccess] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
const [loading, setLoading] = useState(false);
const sendTokens = async (TRANSFER_AMOUNT: number, DESTINATION_WALLET: PublicKey) => {
setLoading(true);
try {
const [sourceAccount, destinationAccount] = await Promise.all([
getOrCreateAssociatedTokenAccount(connection, FROM_KEYPAIR, new PublicKey(MINT_ADDRESS), FROM_KEYPAIR.publicKey),
getOrCreateAssociatedTokenAccount(connection, FROM_KEYPAIR, new PublicKey(MINT_ADDRESS), DESTINATION_WALLET),
]);
const latestBlockHash = await connection.getLatestBlockhash("confirmed");
const sendTransaction = async () => {
const tx = new Transaction();
tx.add(createTransferInstruction(sourceAccount.address, destinationAccount.address, FROM_KEYPAIR.publicKey, TRANSFER_AMOUNT * 1e9));
tx.recentBlockhash = latestBlockHash.blockhash;
tx.feePayer = FROM_KEYPAIR.publicKey;
try {
const signature = await sendAndConfirmTransaction(connection, tx, [FROM_KEYPAIR]);
return signature;
} catch (error) {
console.error("Transaction failed:", error);
throw error;
}
};
let signature;
try {
signature = await sendTransaction();
} catch (error) {
if (error instanceof TransactionExpiredBlockheightExceededError) {
console.log("Transaction expired, retrying...");
signature = await sendTransaction();
} else {
throw error;
}
}
setLoading(false);
setIsOpen(true);
setTransactionUrl(`https://explorer.solana.com/tx/${signature}`);
setSuccess(true);
} catch (error) {
console.error(error);
setLoading(false);
setIsOpen(true);
setErrorMessage(error.message);
}
};
return {
sendTokens,
loading,
isOpen,
errorMessage,
success,
setIsOpen,
transactionUrl,
};
};
export default useSendTokens;
Step 7: Compiling Everything
At this point, you have all the necessary components to make the presale web app functional. Here’s a quick summary of how everything works together:
WalletLayout Component: This wraps your application and provides the Solana connection and wallet functionality to the entire app.
Presale UI: This is the main page where users can connect their wallet, specify the amount of tokens to buy, and initiate a transaction.
useSendTokens Hook: This handles the logic for sending tokens from your wallet to the user’s wallet.
// pages/index.tsx
import { useConnection, useWallet } from '@solana/wallet-adapter-react';
import { useState } from 'react';
import * as web3 from '@solana/web3.js';
import WalletLayout from '../components/WalletLayout';
import { WalletMultiButton } from '@solana/wallet-adapter-react-ui'
const TOKEN_ADDRESS = 'YOUR_RECEIVING_ADDRESS'; // Change this to your token address
const Home = () => {
const { publicKey, sendTransaction } = useWallet();
const { connection } = useConnection();
const [amount, setAmount] = useState(0);
const [txSig, setTxSig] = useState(null);
const handleTransaction = async () => {
if (!publicKey) {
toast.error("Please connect your wallet.");
return;
}
const transaction = new web3.Transaction();
const instruction = web3.SystemProgram.transfer({
fromPubkey: publicKey,
lamports: amount * web3.LAMPORTS_PER_SOL,
toPubkey: new web3.PublicKey(TOKEN_ADDRESS),
});
transaction.add(instruction);
try {
const signature = await sendTransaction(transaction, connection);
setTxSig(signature);
await sendTokens(amount, publicKey);
toast.success("Deposit Successful! Your token will appear in your wallet soon.");
} catch (error) {
console.error(error);
toast.error("Transaction failed.");
}
};
return (
<WalletLayout>
<WalletMultiButton
className='!border-brand-cyan w-full !rounded-[20px] bg hover:!bg-[#161b19] transition-all duration-200'
/>
<div className="container mx-auto">
<h1 className="text-4xl font-bold">Solana Token Presale</h1>
<div className="mt-8">
<p>Token Price: 1 SOL</p>
<input
type="number"
className="mt-4 p-2 border"
placeholder="Amount of tokens"
onChange={(e) => setAmount(parseInt(e.target.value))}
/>
<button
className="mt-4 p-2 bg-blue-500 text-white"
onClick={handleTransaction}
>
Buy Tokens
</button>
</div>
{txSig && <p>Transaction Signature: {txSig}</p>}
</div>
</WalletLayout>
);
};
export default Home;
To see everything in action, run your Next.js app:
npm run dev
Visit http://localhost:3000
in your browser. You should see your presale page with a "Connect Wallet" button. Once you connect your wallet, you can specify the amount of tokens you want to buy and complete the transaction by clicking "Buy Tokens".
Conclusion
You have successfully created a Solana presale web app without writing a smart contract. This guide covered setting up a Next.js app, integrating Solana Wallet Adapter, creating the presale UI, and handling token transactions using the @solana/web3.js
and @solana/spl-token
packages. With this foundation, you can further customize and enhance your presale app to suit your specific requirements. Happy coding!