How to Build and Deploy Your Own Solana dApp

Introduction

Building and deploying a decentralized application (dApp) on the Solana blockchain can seem daunting, but with the right guidance, it’s a manageable and rewarding process. This guide will walk you through the steps to set up your Solana development environment, deploy a program to the devnet, and create a React front-end for your dApp.

Solana

Prerequisites

Before moving further, explore the following article if you have not read it yet.

Setting Up Your Environment
 

Install Prerequisites

Ensure you have the necessary tools installed on your machine:

  • Node.js and npm: For managing packages and running scripts.
  • Solana CLI: For interacting with the Solana blockchain.
  • Anchor: A framework for Solana dApp development.

Install the Solana CLI

sh -c "$(curl -sSfL https://release.solana.com/v1.8.5/install)"

Install Anchor

cargo install --git https://github.com/project-serum/anchor anchor-cli --locked

Configure Your Solana CLI Wallet

Set up and configure your Solana CLI wallet.

solana-keygen new
solana config set --url devnet

Airdrop some tokens to your Devnet wallet.

solana airdrop 2

Develop Your Solana Program
 

Create a New Anchor Project

Initialize a new Anchor project.

anchor init my_solana_dapp
cd my_solana_dapp

Update the Program Code

Modify the program code in the 'programs/my_solana_dapp/src/lib.rs' file to implement your desired functionality. Here’s a simple example of a counter program.

use anchor_lang::prelude::*;
declare_id!("F7nbbscQpQucypsjzJccBDLBSVAVKBHumNLpxqHXym4U");

#[program]
pub mod my_solana_dapp {
    use super::*;
    
    pub fn initialize(ctx: Context<Initialize>) -> ProgramResult {
        let counter = &mut ctx.accounts.counter;
        counter.count = 0;
        Ok(())
    }

    pub fn increment(ctx: Context<Increment>) -> ProgramResult {
        let counter = &mut ctx.accounts.counter;
        counter.count += 1;
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = user, space = 8 + 8)]
    pub counter: Account<'info, Counter>,
    
    #[account(mut)]
    pub user: Signer<'info>,
    
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Increment<'info> {
    #[account(mut)]
    pub counter: Account<'info, Counter>,
}

#[account]
pub struct Counter {
    pub count: u64,
}

Build and Test Your Program

Build the program.

anchor build

Test the program locally.

anchor test

Deploy to Solana Devnet
 

Update Anchor Configuration

Update the 'Anchor.toml' file to use the devnet.

[provider]
cluster = "devnet"
wallet = "/path/to/your/solana/id.json"

[programs.devnet]
my_solana_dapp = "F7nbbscQpQucypsjzJccBDLBSVAVKBHumNLpxqHXym4U"

Deploy the Program

Deploy your program to the devnet.

anchor deploy

Build a React Front-End
 

Set Up Your React Project

Create a new React project using Vite.

npm create vite@latest my_solana_frontend -- --template react-ts
cd my_solana_frontend
yarn

Install Solana and Wallet Adapter Dependencies

Install the necessary dependencies.

yarn add @solana/web3.js @solana/wallet-adapter-base @solana/wallet-adapter-react @solana/wallet-adapter-react-ui @solana/wallet-adapter-wallets @coral-xyz/anchor

Create a Connect Wallet Button

Create a 'ConnectWalletButton' component in 'src/components/ConnectWalletButton.tsx.

import { WalletMultiButton } from '@solana/wallet-adapter-react-ui';
import '@solana/wallet-adapter-react-ui/styles.css';

const ConnectWalletButton = () => {
  return <WalletMultiButton />;
};

export default ConnectWalletButton;

Fetch and Display Data from the Blockchain

Create a 'CounterState' component in 'src/components/CounterState.tsx'.

import { useEffect, useState } from 'react';
import { useConnection } from '@solana/wallet-adapter-react';
import { program, counterPDA, CounterData } from '../anchor/setup';

const CounterState = () => {
  const { connection } = useConnection();
  const [counterData, setCounterData] = useState<CounterData | null>(null);

  useEffect(() => {
    program.account.counter.fetch(counterPDA).then(data => {
      setCounterData(data);
    });

    const subscriptionId = connection.onAccountChange(
      counterPDA,
      accountInfo => {
        setCounterData(program.coder.accounts.decode('counter', accountInfo.data));
      }
    );

    return () => {
      connection.removeAccountChangeListener(subscriptionId);
    };
  }, [connection]);

  return <p className="text-lg">Count: {counterData?.count?.toString()}</p>;
};

export default CounterState;

Increment the Counter

Create an 'IncrementButton' component in 'src/components/IncrementButton.tsx'.

import { useState } from 'react';
import { useConnection, useWallet } from '@solana/wallet-adapter-react';
import { program } from '../anchor/setup';
const IncrementButton = () => {
  const { publicKey, sendTransaction } = useWallet();
  const { connection } = useConnection();
  const [isLoading, setIsLoading] = useState(false);

  const onClick = async () => {
    if (!publicKey) return;

    setIsLoading(true);

    try {
      const transaction = await program.methods.increment().transaction();
      const transactionSignature = await sendTransaction(transaction, connection);
      console.log(`Transaction: ${transactionSignature}`);
    } catch (error) {
      console.error(error);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <button className="w-24" onClick={onClick} disabled={!publicKey}>
      {isLoading ? 'Loading' : 'Increment'}
    </button>
  );
};

export default IncrementButton;

Integrate Components into App

Update 'src/App.tsx' to use the components.

import { useMemo } from 'react';
import { ConnectionProvider, WalletProvider } from '@solana/wallet-adapter-react';
import { WalletAdapterNetwork } from '@solana/wallet-adapter-base';
import { WalletModalProvider } from '@solana/wallet-adapter-react-ui';
import { clusterApiUrl } from '@solana/web3.js';
import ConnectWalletButton from './components/ConnectWalletButton';
import CounterState from './components/CounterState';
import IncrementButton from './components/IncrementButton';
import './App.css';
const App = () => {
  const network = WalletAdapterNetwork.Devnet;
  const endpoint = useMemo(() => clusterApiUrl(network), [network]);
  return (
    <ConnectionProvider endpoint={endpoint}>
      <WalletProvider wallets={[]} autoConnect>
        <WalletModalProvider>
          <ConnectWalletButton />
          <h1>Hello Solana</h1>
          <CounterState />
          <IncrementButton />
        </WalletModalProvider>
      </WalletProvider>
    </ConnectionProvider>
  );
};
export default App;

Run Your Application

Run the React application.

yarn dev

Visit http://localhost:5173 to interact with your deployed Solana program.

Conclusion

Congratulations! You've successfully built and deployed your decentralized application (dApp) on the Solana blockchain. By setting up your Solana development environment, deploying a program to the devnet, and creating a React front-end, you have laid a strong foundation for further development. You can expand your dApp's functionality, integrate more complex features, and even deploy to the Solana mainnet.