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
- Setup environment and install dependencies
First, open VS Code:
Open VS Code press F1, and select connect to WSL.
- Open the terminal View > terminal
- Follow the same previous instructions from Linux/Mac
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
and move to the project's folder by running:
cd Projects
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
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
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
This will install the Solana web3.js library and wallet adapter packages necessary for interacting with the Solana blockchain and handling wallet connections.
- Initialize project and configure file structure, run the following command to test the app:
npm run dev
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
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
And create the files:
touch utils/generator-public-data.ts utils/publicKeyGeneratos.ts
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");
}
}
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
If everything is working as expected, you will see the message in the terminal:
Missing keys generated and saved to .env.local
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"
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}`)
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
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;
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:
- Visit the Phatom Wallet Website and download the extension for you preferred browser.
- Follow the on-screen instructions to create a new wallet or import an existing one.
- 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
Inside it, let's add the following files using this command:
touch src/components/AppBar.tsx src/components/InputFormWallet.tsx src/components/WalletContextProvider.tsx
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>
)
}
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
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>
)
}
- 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
anduseWallet
. -
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
anduseState
are Reach hooks for managing side effects and component state. -
useState(0)
initializes a state variablebalance
with a value of0
. -useConnection
provides access to theconnection
object, which is used to interact with the Solana blockchain. -
useWallet
provides thepublicKey
of the connected wallet. - The 'useEffect' hook runs the code inside whenever 'connection' or 'publicKey' changes.
- If either
connection
orpublicKey
is unavailable, the function exits earlyreturn
. -
connection.onAccountChange
is an event listener that listens for changes in the account associated withpublicKey
. When the account changes, it updates thebalance
state with the new balance (converted from imports to SOL). -
connection.getAccountInfo
fetches the account information forpublicKey
and updates thebalance
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>
)
}
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
anduseWallet
are hooks from@solana/wallet-adapter-react
that allow users to interact with Solana's blockchain and use their wallets. -
React
anduseState
: React imports for building components and managing state. -
web3
: Solana's library to handle blockchain interactions.
Component Definition
-
amount
andrecipient
: 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;
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
andReactNode
: 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 theendpoint
as a prop. -
WalletProvider
: Provides wallet functionality and context to the components. It uses thewallets
array, eventually containing the wallet adapters. Thechildren
Represent the components nested insideWalletContextProvider
, 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;
}
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;
}
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.
Now we have to connect to Phantom wallet:
In the upper right corner, you will see the Phantom Wallet public key:
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 MacCmd +
press simultaneously, and you will see another terminal window to work while the serverlocalhost: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:
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:
After that, send the transaction using are UI to connect to Phantom wallet:
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.
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:
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)