DEV Community

Cover image for Solana Wallet Integration for SOL Transactions
IvanDev
IvanDev

Posted on • Edited on

Solana Wallet Integration for SOL Transactions

Introduction

In previous years, the Solana blockchain and crypto coin "SOL" have gained a strong reputation for building more modern blockchain technologies apart from Bitcoin and Etherium blockchains, adding more advanced features for this current time like high processing speeds, the Proof of History concept to make transactions before timestamps, and the priority for developers to work on top of a layer for fast development.

In this tutorial, we will learn some basic functionality with the Solana standard wallet. In this case, we will use a web-based Phanton Wallet on the devnet network. With this basic app, you can build an app to send SOL to another Solana wallet.

Then, all wallet interactions use npm @solana packages like Solana, @solana/web3.js, @solana/wallet-adapter-react-ui, and others.

We will use React with Next.js to create a basic layout for the UI rendering component. You can add more functionalities or leave them as a template. All these are referenced in the Solana docs.
This tutorial enriched your stack developer with more knowledge of modern blockchain technologies.

Use Cases

  • Users should be able to connect their Solana wallet to the application web extension.
  • Users can send SOL to another address or user using the Phanton Wallet web extension.
  • Users can view their current SOL balance.
  • Users can track SOL once the transaction was made.

Understanding Public and Secret keys in the Phantom Wallet

When using the Phantom Wallet or interacting with it, it's essential to understand the roles of public and secret keys in making transactions. For our tutorial purposes, we only need the Public Key because sending transactions will be handled by the Phantom Wallet, so why the keys:

  • Public Key: The Solana wallet's address ID points to the Solana network. You can share it with others to receive SOL tokens or interact with dApps (decentralized apps).
  • Secret Key: This is the most sensitive key you should keep to access and control your funds. It's used to sign transactions and prove ownership of the funds in your wallet. The Phantom wallet securely stores this key and never exposes it to any external applications, including the one you're building.

Setting Up the Environment

Prerequisites

  • Code Editor of your preference I use VS Code
  • Basic JavaScript
  • Node.js
  • Typescript
  • Code Editor
  • Basic HTML, and CSS

Instructions

  • Setup environment and install dependencies
  • Initialize project and configure file structure

Frontend

  • Configure Solana connection
  • Implement wallet integration
  • Create UI components
  • Handle transactions

Backend

  • Handle transaction and security
  • Testing
  1. Setup environment and install dependencies

First, open VS Code:

Open VS Code

Open VS Code press F1, and select connect to WSL.

Connect WSL

  • Open the terminal View > terminal

Open Terminal

  • Follow the same previous instructions from Linux/Mac

Terminal VS Code
Terminal VS Code

Next, make a folder on your PC, like in a Linux/Mac root folder or Windows.
Linux/Mac

It's common to place your projects in a Projects directory within your home directory.

mkdir -p ~/Projects
Enter fullscreen mode Exit fullscreen mode

and move to the project's folder by running:

cd Projects
Enter fullscreen mode Exit fullscreen mode

Your projects would then be located in path like:
/home/your-username/Projects/my_project (Linux)
/Users/your-username/Projects/my_project (Mac)

Windows Using Linux subsystem WSL

  • Follows same steps as previous on Linux/Mac

Installing Next.js

Initialize the Next.js project, and ensure you're in the project directory:

npx create-next-app@latest wallet-app
Enter fullscreen mode Exit fullscreen mode

It will ask some questions yes to all except in include:

  • Tailwing CSS NO,
  • to App router NO,
  • and import alias (@/*) NO, so then Next.js built all the folder and files for you including .env.local that we will use on next steps.

Move into the project directory:

cd wallet-app
Enter fullscreen mode Exit fullscreen mode

Install required dependencies:

npm install @solana/web3.js @solana/wallet-adapter-base @solana/wallet-adapter-react @solana/wallet-adapter-react-ui @solana/wallet-adapter-wallets
Enter fullscreen mode Exit fullscreen mode

This will install the Solana web3.js library and wallet adapter packages necessary for interacting with the Solana blockchain and handling wallet connections.

  1. Initialize project and configure file structure, run the following command to test the app:
npm run dev 
Enter fullscreen mode Exit fullscreen mode

Run it in your browser most of the time localhost:3000

Note:
While you are coding the app try to see the results in your localhost:3000 to make sure everything it's working as expected.

We have to do the file structure like the following tree structure:

wallet-app/
├── README.md
├── next-env.d.ts
├── next.config.mjs
├── package-lock.json
├── package.json
├── public
│   └── solanaLogo.png       # Any public assets go here
├── src
│   ├── components           # Reusable React components
│   ├── pages                # Next.js pages (e.g., index.tsx)
│   └── styles               # CSS/SCSS files
├── tsconfig.json
└── utils                    # Utility functions and scripts
    └── generator-public-data.ts
Enter fullscreen mode Exit fullscreen mode

Make a folder in the root of the project called utils and create two files inside generator-public.data.ts and publicKeyGenerator.ts that will be used later in the future.

mkdir utils
Enter fullscreen mode Exit fullscreen mode

And create the files:

touch utils/generator-public-data.ts utils/publicKeyGeneratos.ts
Enter fullscreen mode Exit fullscreen mode

Write the following script in Javascript and React to generator-public-data.ts file:

import { Keypair } from "@solana/web3.js";
import * as fs from "fs";
import * as path from "path";

// Define the path to the .env.local file
const envFilePath = path.resolve(__dirname, "../.env.local");

// Function to check if a key exists in the .env.local file
const keyExistsInEnv = (key: string, filePath: string): boolean => {
  if (!fs.existsSync(filePath)) {

    // File doesn't exist, so the key doesn't exist either
    return false; 
  }

  const envFileContent = fs.readFileSync(filePath, "utf-8");
  return envFileContent.includes(`${key}=`);
};

// Check if the keys already exist in the .env.local file
const programIdKey = "NEXT_PUBLIC_PROGRAM_ID";
const dataAccountPubKeyKey = "NEXT_PUBLIC_DATA_ACCOUNT_PUBKEY";

const programIdExists = keyExistsInEnv(programIdKey, envFilePath);
const dataAccountPubKeyExists = keyExistsInEnv(dataAccountPubKeyKey, envFilePath);

if (programIdExists && dataAccountPubKeyExists) {
  console.log("Keys already exist in .env.local. No action taken.");
} else {
  // Generate and append only the keys that don't already exist
  let envContent = "";

  if (!programIdExists) {
    const programKeypair = Keypair.generate();
    const programId = programKeypair.publicKey.toBase58();
    envContent += `${programIdKey}=${programId}\n`;
  }

  if (!dataAccountPubKeyExists) {
    const dataAccountKeypair = Keypair.generate();
    const dataAccountPubKey = dataAccountKeypair.publicKey.toBase58();
    envContent += `${dataAccountPubKeyKey}=${dataAccountPubKey}\n`;
  }

  // Append the new content to the .env.local file if necessary
  if (envContent) {
    fs.appendFileSync(envFilePath, envContent.trim());
    console.log("Missing keys generated and saved to .env.local");
  }
}

Enter fullscreen mode Exit fullscreen mode

Explanation:

This script is an enhanced utility for generating and managing cryptographic keys in your Solana wallet application. It automates generating keys and adequately stores them in the .env.local file.

Here's a step-by-step explanation:

PublicKey: This is the identification and when somebody transfers funds from.
Data Account Public Key: This is where you save all related transactions and contract information.

  • First, we import the Solana web3 package to get the Keypair function to generate the keys; then, we import the fs File System Module to file reading, writing, and manipulation.
  • Make the path module resolves file paths and cross-platform ways.
  • We define a function keyExistsInEnv() to check if any keys are already in the .env.local file.
  • Save constant values to use with the keyExistsInEnv() function.
  • Saves the result of the keyExitsInEnv() function to constant programIdExist.
  • Generate and save the keys if necessary: If the keys exist, no action is taken.
  • Key generation: if no keys inside the .env.local file, the script will generate the keypairs.
  • Program ID: if the NEXT_PUBLIC_PROGRAM_ID is missing, the script will generate the key.
  • Data Account Public Key: If the key is missing, the script will generate the key.
  • Appending to .env.local: If missing, the new keys are only appended to the .env.local file. This ensures that existing keys aren't overwritten and that only the missing ones are generated.

Ensure that the .env.local is in the root directory so we can follow along.

Then, ensure the .env.local is in the directory's root. Next.js usually creates us automatically, and the .gitignore files to be ignored will be added there once the project is created from the beginning of the setup project.

It's the most extended script in the app, but it's straightforward, as you can see.

After this, we can generate the keys out of thin air like magic:

Now, run the following command to generate the public keys in the root's project folder:

npx esrun utils/generator-public-data.ts
Enter fullscreen mode Exit fullscreen mode

If everything is working as expected, you will see the message in the terminal:

Missing keys generated and saved to .env.local
Enter fullscreen mode Exit fullscreen mode

Check the .env.local in the root's project structure for the generate it public key strings values:

NEXT_PUBLIC_PROGRAM_ID="YourGeneratedProgramID"
NEXT_PUBLIC_DATA_ACCOUNT_PUBKEY="YourGeneratedDataAccountPubKey"
Enter fullscreen mode Exit fullscreen mode

publicKeyGenerator.ts

import {Keypair} from "@solana/web3.js"

const keypair2 = Keypair.generate()

console.log(`The public key 2 is: ${keypair2.publicKey.toBase58()}`)
console.log(`The secret key 2 is: ${keypair2.secretKey}`)
Enter fullscreen mode Exit fullscreen mode

Explanation

This silly, tiny script basically imports the Keypair object from the Solana web3 library. Next, it generates a keypair from the generate() method of the Keypair object and saves it in a keypair variable.
The log the result pairs one: it's a publicKey of 58 chars and a secretKey with an array of numbers; in this tutorial, for testing purposes, don't worry about the secretKey. In real life, in the mainnet Solana blockchain, you have to save it in another safe place.

We will use this script at a later time in the project.

The next instruction from the program's design is the 'frontend' part. This also includes the backend. It's a small project, but you can modularize more. These are just the basic concepts to grasp.

Building the components

In the root's folder structure, expand src and pages, and you will see two files: _app.tsx and index.tsx erase all content from it, and we will implement the following code into those files:

Next.js main entry point app _app.tsx

_app.tsx

import '../styles/globals.css'

function MyApp({ Component, pagePros}) {
  return <Component {...pagePros} />
}

export default MyApp
Enter fullscreen mode Exit fullscreen mode

This file includes global CSS to render the appropriate component for the current route.

The main page of your application index.tsx

index.tsx

import { NextPage } from "next"
import styles from '../styles/Home.module.css'
import { AppBar } from "@/components/AppBar"
import Head from "next/head"
import WalletContextProvider from "@/components/WalletContextProvider"
import { BalanceDisplay } from "@/components/BalanceDisplay"
import { InputFormWallet } from "@/components/InputFormWallet"


const Home: NextPage = (props) => {
  return (
    <div className={styles.App}>
      <Head>
        <title>Wallet-Adapter Example</title>
        <meta
          name="description"
          content="Wallet-Adapter Example"
        />
      </Head>
      <WalletContextProvider>
        <AppBar />
        <div className={styles.AppBody}>
          <BalanceDisplay />
          <InputFormWallet />
        </div>
      </WalletContextProvider>
    </div>
  )
}

export default Home;

Enter fullscreen mode Exit fullscreen mode

This is your application's main page. It integrates all the components to display a functional UI for interacting with Solana wallets and features.
The Home component renders the AppBar, BalanceDisplay, and 'InputFormWallet' components, all wrapped inside the WalletContextProvider.

Connect Your Wallet

Before diving into the technical steps, ensure you have the Phantom wallet installed and set up. Phantom is a popular Solana wallet that you'll use to interact with the Solana blockchain

How to open and Set Up Phantom Wallet:

  1. Visit the Phatom Wallet Website and download the extension for you preferred browser.
  2. Follow the on-screen instructions to create a new wallet or import an existing one.
  3. Once set up, open the Phantm wallet extension and ensure it is connected to the Solana network; in the upper left side, click on config and follow preferences, and then move to devnet.

In the Phantom wallet, you have your own public key where you receive tokens. In this case, test tokens are not real.
Once you open again your Phantom Solana wallet in the upper right corner, once you connect and enter the password, then click on the button and copy that public key address.
In order to have some test tokens, go to Solana Faucet to get some tokens to test paste your public key there and get some tokens.

Remember, you can send these new test tokens to another public key address for testing purposes. That's the reason we did the previous script to generate a public key address, as well as a secret one, but we won't use it in this project because we are using Phantom wallet, which has its own secret key.

In Chrome, you will see the extension jigsaw symbol "not the movie" 😄 in the upper right corner. Open it and pin the "Phantom wallet" extension to the main bar so that the next time you enter, it's your sight right away.

UI Components and Connection to Wallet

Before we further create a new folder in the src folder:

mkdir src/components
Enter fullscreen mode Exit fullscreen mode

Inside it, let's add the following files using this command:

touch src/components/AppBar.tsx src/components/InputFormWallet.tsx src/components/WalletContextProvider.tsx
Enter fullscreen mode Exit fullscreen mode

AppBar.tsx

This component shows the application's top navigation bar, which includes a logo, title, and wallet connection button.

import { FC } from "react";
import styles from '../styles/Home.module.css'
import Image from "next/image";
import dynamic from "next/dynamic";
const WalletMultiButton = dynamic(() => import('@solana/wallet-adapter-react-ui').then(mod => mod.WalletMultiButton), { ssr: false });


export const AppBar: FC = () => {
    return(
        <div className={styles.AppHeader}>
            <Image src="/solanaLogo.png" height={30} width={200} alt="logo-image"/>
            <span>Wallet-Adapter Example</span>
            <WalletMultiButton />
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

As always, test the result in localhost:3000 to see the desired behavior.

The component imports the WalletMultiButton dynamically, which is used to connect to various Solana wallets. This button will allow users to connect their wallets to the application.
The styles object is imported from the Home.module.css file, which provides CSS styling for the component.
The logo is rendered using the Next.js image component for optimized image loading.

Copy this image from your browser or download it to place in wallet-app/public/solanaLogo.png

Solana Logo

BalanceDisplay.tsx

import { useConnection, useWallet } from "@solana/wallet-adapter-react"
import { LAMPORTS_PER_SOL } from "@solana/web3.js"
import { FC, useEffect, useState } from 'react'

export const BalanceDisplay: FC = () => {
    const [balance, setBalance] = useState(0);
    const { connection } = useConnection();
    const { publicKey } = useWallet();

    useEffect(() => {
        if (!connection || !publicKey) {
            return;
        }

        connection.onAccountChange(
            publicKey,
            updatedAccountInfo => {
                setBalance(updatedAccountInfo.lamports / LAMPORTS_PER_SOL)
            },
            "confirmed",
        );

        connection.getAccountInfo(publicKey).then(info => {
            setBalance(info.lamports);
        });
    }, [connection, publicKey]);

    return(
        <div>
            <p>{publicKey ? `Balance ${balance / LAMPORTS_PER_SOL}` : ""} </p>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode
  • Displays the balance of SOL (Solana) tokens in the connected wallet in the UI.
  • Imports all hooks required to interact with Solana wallets, like useConnection and useWallet.
  • LAMPORTS_PER_SOL is a constant from @solana/web3.js representing the number of lamports (the smallest unit of SOL) per SOL (1 SOL = 1,000,000,000 lamports).
  • FC stands for Function component, a type in React. useEffect and useState are Reach hooks for managing side effects and component state.
  • useState(0) initializes a state variable balance with a value of 0. -useConnection provides access to the connection object, which is used to interact with the Solana blockchain.
  • useWallet provides the publicKey of the connected wallet.
  • The 'useEffect' hook runs the code inside whenever 'connection' or 'publicKey' changes.
  • If either connection or publicKey is unavailable, the function exits early return.
  • connection.onAccountChange is an event listener that listens for changes in the account associated with publicKey. When the account changes, it updates the balance state with the new balance (converted from imports to SOL).
  • connection.getAccountInfo fetches the account information for publicKey and updates the balance state with the initial balance. Lastly, it's the return to render the content's component.

InputFormWallet.tsx

This file is the body of the page where you see all the functionality to interact with the external Phantom wallet, in this case, with user inputs.

import { useConnection, useWallet } from "@solana/wallet-adapter-react"
import React, { FC, useState } from 'react'
import * as web3 from "@solana/web3.js"
import styles from '../styles/Home.module.css'

export const InputFormWallet: FC = () => {
    const [amount, setAmount] = useState<string>("")
    const [recipient, setRecipient] = useState<string>("")
    const { connection } = useConnection()
    const { publicKey, sendTransaction } = useWallet()
    const [transactionLink, setTransactionLink] = useState<string>("")

    const handleAmountChange = (event: React.ChangeEvent<HTMLInputElement>) => {
        setAmount(event.target.value)
    }

    const handleRecipientChange = (event: React.ChangeEvent<HTMLInputElement>) => {
        setRecipient(event.target.value)
    }

    const handleSubmit = async (event: React.FormEvent) => {
        event.preventDefault()

        if (!connection || !publicKey) {
            console.error("Connection or publickey is not available.")
            return
        }

        try {
            const lamports = parseFloat(amount) * web3.LAMPORTS_PER_SOL
            const recipientPubKey = new web3.PublicKey(recipient)
            const transaction = new web3.Transaction()

            const sendSolInstruction = web3.SystemProgram.transfer({
                fromPubkey: publicKey,
                toPubkey: recipientPubKey,
                lamports,
            })

            transaction.add(sendSolInstruction)


            const signature = await sendTransaction(transaction, connection)
            const explorerUrl = `https://explorer.solana.com/tx/${signature}?cluster=devnet`
            setTransactionLink(explorerUrl)

        } catch (error) {
            console.error("Transaction failed:", error)
        }
    }

    return (
        <div className={styles.AppBody}>
            <form onSubmit={handleSubmit} className={styles.form}>
                <p>Amount (in SOL) to send:</p>
                <input
                    type="text"
                    value={amount}
                    onChange={handleAmountChange}
                    placeholder="Enter amount"
                    className={`${styles.input} ${styles.formField}`}
                />
                <br />
                <p>Send SOL to:</p>
                <input
                    type="text"
                    value={recipient}
                    onChange={handleRecipientChange}
                    placeholder="Enter recipient address"
                    className={`${styles.input} ${styles.formField}`}
                />
                <br />
                {transactionLink && (
                    <a 
                    href={transactionLink} 
                    target="_blank"
                    className={styles.transactionLink}
                    >
                        Check transaction at Solana Explorer
                    </a>

                )}
                <br />
                <button type="submit" className={`${styles.input} ${styles.formField}`}>Send SOL</button>
            </form>

        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

This is a long explanation with all the steps included in each part's file to fully understand the process of handling transactions.

Imports

  • useConnection and useWallet are hooks from @solana/wallet-adapter-react that allow users to interact with Solana's blockchain and use their wallets.
  • React and useState: React imports for building components and managing state.
  • web3: Solana's library to handle blockchain interactions.

Component Definition

  • amount and recipient: State variables to store the amount of SOL to send and the recipient's address.
  • connection: Provides the connection to the Solana blockchain.
  • publicKey: The user's public key from the wallet.
  • sendTransaction: Function to send a transaction to the Solana blockchain.
  • transationLink: State variable to store the URL transaction URL on Solana Explorer.

Handlers

Then, the handleAmountChange function calculates the imports (these ones are like satoshi in Bitcoin) or something similar from amount to convert SOL to imports (the smallest unit of SOL). So far, so good.

Next is the handleRecipientChange function, which updates the recipient state when the user types the recipient address input field.

Form Submission

In the beginning, we used the handleSubmit function to handle form submissions, which has the transaction functionality to check if the connection and public key exist.

After that, we used our beloved try-catch block for better debugging practices. We placed all the logic inside the try and initialized the constant variables.

Convert lamports from the amount state variable to lamport (the smallest unit of SOL). Next, we create the recipientPubKey to convert the recipient address to a PublicKey object and save it to a constant variable transaction to construct a new transaction object.

Here, we use a helper function from the 'w3' Solana library to transfer some SOL and chain with the transfer method with the address from the public key and to the recipient address public key. All of these are saved in the sendSolIntruction constant variable.

Then, execute the instructions with sendsolInstructions, chaining the method 'add' from transaction constant variable.

To check if the transaction was successful and if it didn't catch any errors in the process. In blockchain operations in most cryptos, we see that process checking the result of the transaction, the tx, the transaction validation or signature, which is a long string of alphanumeric values.

Give the arguments 'transaction' and 'connection' to the sendTransaction function from Solana web3. This asynchronous operation saves to a constant variable, signature.
Now that we have our signature, we save it to show the result of building a URL, which we call a variable explorerUrl.
Set the explorerUrl as argument in the setTransactionLink state hook.

Rendering

  • Form: a form that triggers 'handleSubmit' on submission.
  • Amount Input: Field for entering the amount of SOL to send.
  • Recipient Input: Field for entering the recipient's public address or address to send the SOL tokens.
  • Submit Button: Button to submit the form and send SOL.

This file has all the app's main logic. That's why the explanation took centuries.

WalletContextProvider

import { FC, ReactNode, useMemo } from "react";
import {
    ConnectionProvider,
    WalletProvider,
} from "@solana/wallet-adapter-react"
import { WalletModalProvider} from "@solana/wallet-adapter-react-ui"
import * as web3 from "@solana/web3.js";
require("@solana/wallet-adapter-react-ui/styles.css")

const WalletContextProvider: FC<{ children: ReactNode }> = ({ children }) => {
    const endpoint = web3.clusterApiUrl("devnet");
    const wallets = useMemo(() => [], [])

    return (
      <ConnectionProvider endpoint={endpoint}>
        <WalletProvider wallets={wallets}>
          <WalletModalProvider>{children}</WalletModalProvider>
        </WalletProvider>
      </ConnectionProvider>
    );
  };

export default WalletContextProvider;
Enter fullscreen mode Exit fullscreen mode

This code file integrates the Solana wallet functionality into our React Next application. It ensures our app can connect to the Solana blockchain and interact with various Solana wallets.

Imports

  • FC and ReactNode: Typescript types for defining functional components and their children.
  • useMemo: A React hook for memoizing(not repeating, speed up) values to optimize performance.
  • ConnectionProvider, WalletProvider: Providers from @solana/wallet-adapter-react to handle Solana blockchain connections and wallet interactions.
  • WalletModalProvider: Provides a modal UI for wallet selection from @solana/wallet-adapter-react-ui.
  • web3: Solana library for interacting with the blockchain.
  • require("@solana/wallet-adapter-react-ui/styles.css"): Imports the default styles for the wallet UI components.

Explanation

The WalletContextProvider is A functional component that takes children as a prop. Those children represent the nested components that this provider will wrap.

Next, to interact with the blockchain, call the devnet Solana blockchain from the clusterApiUrl function from the web3 library.
If it were a real transaction, it has to be the mainnet blockchain.

Another variable here is wallets, a memoized empty array—adapters to specify which wallets your application supports.

Rendering

  • ConnectionProvider: Wraps the application and connects to the Solana blockchain. It takes the endpoint as a prop.
  • WalletProvider: Provides wallet functionality and context to the components. It uses the wallets array, eventually containing the wallet adapters. The children Represent the components nested inside WalletContextProvider, which will have access to the Solana connection and wallet functionality.

Finally, export the component.

Style the page's body:

In the file structure in your VS Code panel or your code of choice, localize the folder: src/styles and then open global.css and erase all the content and place this one standard CSS code:

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}
Enter fullscreen mode Exit fullscreen mode

Do the same with the following file in src/styles and the open Home.module.css erase all and copy this code:

.App {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  text-align: left;
  background-color: #282c34;
}

.AppHeader {
  height: 90px;
  display: flex;
  background-color: black;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  font-size: 50px;
  color: white;
  padding-left: 20px;
  padding-right: 20px;
  flex-wrap: wrap;
}

.AppBody {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  font-size: calc(10px + 2vmin);
  color: white;
  padding-top: 50px;
}

.form {
  display: flex;
  flex-direction: column;
  align-items: center;
  border: 1px solid #ccc;
  border-radius: 8px;
  padding: 20px;
  background-color: #333;
}

.input {
  background: #f2f2f2;
  padding: 10px;
  width: 100%;
  max-width: 300px;
  border-radius:  4px;
  border: 1px solid #ccc;
  font-size: 16px;
}

.formField {
  margin: 10px 0;
  width: 100%;
  transition: border-color 0.3s ease, box-shadow 0.3s ease;
}
.formField:hover {
  border-color: #0070f3;
  box-shadow: 0 0 5px rgba(0, 122, 243, 0.5);
}

.formButton {
  margin: 10px 0;
  padding: 10px 20px;
  font-size: 16px;
  border: none;
  border-radius: 4px;
  background-color: #0070f3;
  color: #fff;
  cursor: pointer;

}


.transactionLink {
  color: #61dafb;
  text-decoration: none;
  margin-top: 5px;
  font-size: 20px;
  transition: color 0.3s ease;
}
.transactionLink:hover {
  color: #21a1f1;
}

Enter fullscreen mode Exit fullscreen mode

All the previous CSS files were to style the body and buttons of the main app's index.tsx` file.

Testing

  • Check results localhost:3000 in your favorite browser.

UI and Phantom Wallet

Now we have to connect to Phantom wallet:

Connect Phantom Wallet

In the upper right corner, you will see the Phantom Wallet public key:

Balance Publickey Phantom wallet

Now, let's do the main work of this project to send SOL(solana tokens) test tokens as we are using devnet blockchain not real.

-First, input some SOL token to send, in this case, just 0.1:

  • Remember the file that we did earlier in the project: at src/utils/publicKeyGenerator.ts, open another terminal with this shortcut in Windows: Ctrl+Shift+ or in Mac Cmd + press simultaneously, and you will see another terminal window to work while the server localhost:3000 it's working.
  • In that terminal type this command: bash cd Projects/wallet-app And then, also type this command: bash npx esrun utils/publicKeyGenerator.ts You will see his result in the terminal console:

Generated PublicKey Address

Underneath the public key, you see the secret key and a warning message from punny module. For now, just focus on the public key. Those are warnings that once you have to deploy, you will clean it. There is nothing to worry about for now.

Copy Windows Ctrl + c Mac Cmd + c and paste in the the input "Send SOL to:

Paste Solana Address

After that, send the transaction using are UI to connect to Phantom wallet:

Press send button

That precious action will open the 'Phantom Wallet', and you will see too some warning messages. Don't worry about those since this is testing in real life you have to clean it. Just click on the 'Confirm' button.

Phantom Wallet window transaction

The 'Phantom' window disappears after that, and you will see a blue message between the address we sent the tokens for and the Send SOL button:

Solana transaction tx

That action will open a new window, the 'Solana Explorer'. It's a web page where you can see your transaction results in action with all the information like:

  • Signature
  • Result
  • Timestamp
  • Confirmation Status
  • Number of confirmations

With, all that information, you already know if the transaction was made or not. Remember that this the 'Devnet'; for testing in production app we have to work on 'Mainnet' blockchain.

Back to our UI window, you can see your remaining SOL's balance.

It was a long tutorial please take your time to do it ufffff.

Summary

This project tutorial explains how to create a simple Solana wallet app using React, TypeScript, and the Solana Web3.js library. The app allows user to connect their wallets, display their SOL balance, and send transitions to other Solana addresses. Key components like BalanceDisplay, InputFormWallet, and WalletContextProvider are covered, along with the necessary imports, state management, and form handling.

The app's styling is also provided to ensure a smooth user interface.

I used all the references from Solana's Official web page.
I added some more functionality scripts so you can be comfortable working with this project tutorial.

Conclusion

By following this project tutorial, you've taken the first step toward building decentralized applications on the Solana blockchain. This basic Solana wallet app demonstrates how to connect to a Solana wallet, display the user's balance, and send transactions using React and TypeScript. The integration with Solana Web3.js introduces essential blockchain interactions, offering a practical introduction to working with decentralized finance(DeFi) and Web3 technologies.

Download the project at: Github Wallet-App

References

About the Author

Ivan Duarte is a backend developer with experience working freelance. He is passionate about web development and artificial intelligence and enjoys sharing their knowledge through tutorials and articles. Follow me on X, Github, and LinkedIn for more insights and updates.

Top comments (0)