diff --git a/fern/api-reference/node-api/chain-apis-overview.mdx b/fern/api-reference/node-api/chain-apis-overview.mdx index 2d768722a..746be0708 100644 --- a/fern/api-reference/node-api/chain-apis-overview.mdx +++ b/fern/api-reference/node-api/chain-apis-overview.mdx @@ -1296,7 +1296,7 @@ Dive into each API's detailed documentation by following the links below, organi ## Solana APIs -📙 Get started with our [Solana API Quickstart Guide](/docs/reference/solana-api-quickstart). +📙 Get started with our [Solana API Quickstart Guide](/docs/solana). | | | | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | diff --git a/fern/api-reference/solana/accounts-db-infra.mdx b/fern/api-reference/solana/accounts-db-infra.mdx new file mode 100644 index 000000000..6ae12faef --- /dev/null +++ b/fern/api-reference/solana/accounts-db-infra.mdx @@ -0,0 +1,68 @@ +--- +title: Leverage AccountsDB Infrastructure for Solana RPC Requests +description: Leverage Alchemy’s AccountsDB Infrastructure for Solana RPC Requests! +subtitle: Leverage Alchemy’s AccountsDB Infrastructure for Solana RPC Requests! +slug: docs/solana/accounts-db-infra +--- + +The most expensive Solana RPC requests involve account scans, such as `getProgramAccounts` and `getLargestTokenAccounts`. These methods are incredibly useful but non-performant methods since they induce a heavy RPC load on Solana validator nodes, often resulting in a 5XX response due to timeout or a response with high latency. + +Alchemy has built out core infrastructure that sits atop our Solana validator nodes to support these methods at scale, through what we call the AccountsDB Infrastructure. This infrastructure allows for **faster**, **scalable**, and more **reliable** responses to these methods by paginating the response with a `pageKey`. You could then loop through your that same request with at scan and aggregate the full response from our validator nodes. + +You can see `pageKey` is now an optional parameter in each account-scanning method in our Solana [docs](/reference/getprogramaccounts), and you may also include an `order` optional parameter that would sort the accounts in the response by their `pubkey` field. + +Here's an example with `getProgramAccounts`: + + + ```typescript typescript + const axios = require("axios"); + + async function getProgramAccountsExample() { + let gPAExampleRequest = { + method: "getProgramAccounts", + params: [ + "ZETAxsqBRek56DhiGXrn75yj2NHU3aYUnxvHXpkf3aD", + { + encoding: "base64", + withContext: true, + order: "desc", + dataSlice: { + offset: 0, + length: 10, + }, + }, + ], + id: 0, + jsonrpc: "2.0", + }; + let programAccounts = []; + + const alchemyRPCUrl = + "https://solana-mainnet.g.alchemy.com/v2/"; + try { + let response = await axios.post(alchemyRPCUrl, gPAExampleRequest); + let responseData = response.data["result"]; + + // continue aggregating if there's a new pageKey present in the latest response + while (responseData["pageKey"]) { + programAccounts = programAccounts.concat(responseData["value"]); + + // place the pagekey within the optional config object + // (you may need to create that config object if you didn't have it originally) + gPAExampleRequest["params"][1]["pageKey"] = responseData["pageKey"]; + + // make another call to getProgramAccounts with the pageKey + response = await axios.post(alchemyRPCUrl, gPAExampleRequest); + responseData = response.data["result"]; + } + + programAccounts = programAccounts.concat(responseData["value"]); + console.log(programAccounts); + return programAccounts; + } catch (err) { + console.log(err.message); + return []; + } + } + ``` + diff --git a/fern/api-reference/solana/hello-world-application.mdx b/fern/api-reference/solana/hello-world-application.mdx new file mode 100644 index 000000000..27b68fe18 --- /dev/null +++ b/fern/api-reference/solana/hello-world-application.mdx @@ -0,0 +1,272 @@ +--- +title: Set up Frontend for Solana Application +description: Step-by-step guide to integrating, calling, and interacting with a Solana on-chain program using Rust and Alchemy's Solana RPC from your own application. +subtitle: Integrate, call, and interact with your Solana on-chain program using Rust and Alchemy RPC +slug: docs/hello-world-solana-application +--- + +You can check out the project in two ways: + +* **GitHub Repository:** [alchemyplatform/solana-hello-world-2025](https://github.com/alchemyplatform/solana-hello-world-2025) — Clone this repo to explore or use the code yourself. +* **Live Demo:** [Deployed Application](https://solana-hello-world-2025.vercel.app/) — Interact with the app directly in your browser. + +## Step 1: Get your Solana program ID + +[The previous guide](/docs/hello-world-solana-program) shows you how to deploy a program to Solana devnet. Using that guide, we got the following program id: + +```bash +Eq5z52U3gGZNHVhgR1bgba8deMgtuFkpUNzd8iBsKvwJ +``` + +You can use this in the examples below, or replace it with your own. + +## Step 2: Create a Next.js app + +From your terminal, run: + +```bash +npx create-next-app@latest solana-hello-frontend \ + --typescript \ + --eslint \ + --app \ + --src-dir \ + --tailwind \ + --import-alias "@/*" + +cd solana-hello-frontend +``` + +This sets up: + +* Next.js with the App Router +* TypeScript +* Tailwind (optional, but nice for styling) + +*** + +## Step 3: Install Solana web3.js and set environment variables + +Install the Solana SDK: + +```bash +npm install @solana/web3.js +``` + +Create a file called `.env` in the root of `solana-hello-frontend` folder: + +```bash +touch .env +``` + +Add your Alchemy RPC URL and program ID: + +```bash +NEXT_PUBLIC_ALCHEMY_RPC_URL="https://solana-devnet.g.alchemy.com/v2/YOUR_ALCHEMY_API_KEY" +NEXT_PUBLIC_PROGRAM_ID="Eq5z52U3gGZNHVhgR1bgba8deMgtuFkpUNzd8iBsKvwJ" +``` + +> In a real app, replace the example program ID with the one from your own deployment. + +## Step 4: Build a minimal client UI that pings your program + +We’ll make the homepage (app/page.tsx) a client component that: + +* connects a Solana wallet (e.g. Phantom) + +* builds a transaction with an instruction calling your program + +* sends it via Alchemy RPC + +* shows the signature + status + +Open `src/app/page.tsx` and replace its contents with: + +```typescript +"use client"; + +import { useState } from "react"; +import { + Connection, + PublicKey, + Transaction, + TransactionInstruction, +} from "@solana/web3.js"; + +const RPC_URL = process.env.NEXT_PUBLIC_ALCHEMY_RPC_URL as string; +const PROGRAM_ID = process.env.NEXT_PUBLIC_PROGRAM_ID as string; + +declare global { + interface Window { + solana?: any; // Phantom or compatible wallet + } +} + +export default function Home() { + const [walletAddress, setWalletAddress] = useState(null); + const [txSignature, setTxSignature] = useState(null); + const [status, setStatus] = useState(null); + const [loading, setLoading] = useState(false); + + const connectWallet = async () => { + try { + if (!window.solana) { + alert("No Solana wallet found. Please install Phantom or a compatible wallet."); + return; + } + + const resp = await window.solana.connect(); + setWalletAddress(resp.publicKey.toString()); + setStatus("Wallet connected."); + } catch (err) { + console.error(err); + setStatus("Failed to connect wallet."); + } + }; + + const pingProgram = async () => { + if (!walletAddress) { + setStatus("Connect your wallet first."); + return; + } + + if (!RPC_URL || !PROGRAM_ID) { + setStatus("Missing RPC URL or PROGRAM_ID env vars."); + return; + } + + try { + setLoading(true); + setStatus("Sending transaction..."); + setTxSignature(null); + + const connection = new Connection(RPC_URL, "confirmed"); + const provider = window.solana; + + const programId = new PublicKey(PROGRAM_ID); + const userPublicKey = new PublicKey(walletAddress); + + // Build an instruction that calls your Hello World program + const instruction = new TransactionInstruction({ + programId, + keys: [ + { + pubkey: userPublicKey, + isSigner: true, + isWritable: false, + }, + ], + // Your Hello World program ignores instruction data, so this can be empty + data: Buffer.from([]), + }); + + const transaction = new Transaction().add(instruction); + + // Set fee payer and recent blockhash + transaction.feePayer = userPublicKey; + const latestBlockhash = await connection.getLatestBlockhash(); + transaction.recentBlockhash = latestBlockhash.blockhash; + + // Ask the wallet to sign the transaction + const signedTx = await provider.signTransaction(transaction); + + // Send to the network through Alchemy RPC + const signature = await connection.sendRawTransaction(signedTx.serialize()); + setTxSignature(signature); + setStatus("Transaction sent. Waiting for confirmation..."); + + // Wait for confirmation + await connection.confirmTransaction( + { + signature, + blockhash: latestBlockhash.blockhash, + lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, + }, + "confirmed" + ); + + setStatus("✅ Success! Your program was invoked."); + } catch (err) { + console.error(err); + setStatus("❌ Error sending transaction. Check the browser console for details."); + } finally { + setLoading(false); + } + }; + + return ( +
+
+

+ Solana Hello World 👋 +

+

+ Connect your wallet and ping your on-chain Hello World program on{" "} + devnet using Alchemy RPC. +

+ + {!walletAddress ? ( + + ) : ( + <> +
+ Connected:{" "} + {walletAddress} +
+ + + )} + + {status && ( +

+ {status} +

+ )} + + {txSignature && ( +

+ Tx Signature:{" "} + + View on Solana Explorer + +

+ )} +
+
+ ); +} +``` + +## Step 5: Run the Next.js app + +```bash +npm run dev +``` + +# 🎉 Success + +You now have a working Solana program deployed on Solana devnet! + +Check out the next guide on how to: + +1. set up a frontend for this program +2. invoke it using Alchemy 🚀 diff --git a/fern/api-reference/solana/hello-world.mdx b/fern/api-reference/solana/hello-world.mdx new file mode 100644 index 000000000..0b6593db3 --- /dev/null +++ b/fern/api-reference/solana/hello-world.mdx @@ -0,0 +1,237 @@ +--- +title: Build & Deploy a "Hello World" Solana Program +description: Step-by-step guide to building, deploying, and calling a minimal Solana on-chain program using Rust and Alchemy's Solana RPC. +subtitle: Create, deploy, and call your first on-chain Solana program using Rust and Alchemy RPC +slug: docs/hello-world-solana-program +--- + +This guide walks you through building, deploying, and calling a minimal +Solana on-chain program using **Rust** and **Alchemy's Solana RPC**.\ +If you're new to Solana development, this is the perfect first step to +validate your toolchain and RPC setup. + +*** + +## Overview + +In this tutorial, you will: + +1. Install Solana + Rust tooling +2. Scaffold a new Rust program +3. Write a modern, minimal Solana program +4. Build it to BPF +5. Deploy to devnet +6. Invoke it with a TypeScript client using Alchemy RPC +7. View logs proving the program ran correctly + +*** + +## Prerequisites + +### ✔ Rust & Cargo + +```bash +curl https://sh.rustup.rs -sSf | sh +``` + +### ✔ Solana CLI + +```bash +cargo install solana-cli +``` + +### ✔ Node.js + Yarn or PNPM + +```bash +npm install -g pnpm +``` + +### ✔ Alchemy Solana RPC URL + +From Alchemy dashboard → **Create App** → **Solana** → **devnet**. + +*** + +## Step 1: Create a new Solana program + +```bash +mkdir solana-hello-world +cd solana-hello-world +cargo new --lib hello_world +``` + +*** + +## Step 2: Add Solana dependencies + +Open `Cargo.toml` and replace the contents with: + +```toml +[package] +name = "hello_world" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +solana-program = "1.18.0" +``` + +*** + +# Step 3: Write the Hello World program + +Put this into `src/lib.rs`: + +```rust +use solana_program::{ + account_info::{next_account_info, AccountInfo}, + entrypoint, + entrypoint::ProgramResult, + msg, + pubkey::Pubkey, +}; + +entrypoint!(process_instruction); + +fn process_instruction( + _program_id: &Pubkey, + accounts: &[AccountInfo], + _instruction_data: &[u8], +) -> ProgramResult { + let accounts_iter = &mut accounts.iter(); + let signer = next_account_info(accounts_iter)?; + + msg!("👋 Hello, Solana from Alchemy!"); + msg!("Invoked by signer: {}", signer.key); + + Ok(()) +} +``` + +*** + +# Step 4: Build the program + +```bash +cargo build-sbf +``` + +To test this command was successful, run `ls target/deploy` - this should output something similar to: + +```bash +hello_world.so +hello_world-keypair.json +``` + +*** + +# Step 5: Create a Solana keypair + +```bash +solana-keygen new +``` + +You'll get: + +```bash +Wrote new keypair to /Users/al/.config/solana/id.json +======================================================================== +pubkey: 7QGE8McfeCHgm12Q8VN6jgBKqbbVdKpq5YiQrTyiMiNJ +======================================================================== +``` + +This wallet will pay for your deployments! + +*** + +# Step 6: Set your network to devnet + +Use Alchemy: + +```bash +solana config set --url https://solana-devnet.g.alchemy.com/v2/Pq8ZtHk2Gf3J4BH30ru_k +``` + +Your terminal will output a confirmation message. ✅ + +*** + +# Step 7: Airdrop SOL + +```bash +solana airdrop 0.2 +``` + +> Note: You may get a rate-limit error. If you do and need more devnet funds, try [the official Solana faucet](https://faucet.solana.com/). + +*** + +# Step 8: Deploy your program + +```bash +solana program deploy target/deploy/hello_world.so +``` + +Your terminal will output something like: + +```bash +Program Id: Eq5z52U3gGZNHVhgR1bgba8deMgtuFkpUNzd8iBsKvwJ + +Signature: yhJxt2ovd3SzGRZbhTkWajkA3uvhX8iqLzuWSrPYyW6AAqZjD2Vq1ApxSAUS5ywQyUnwDwPG8vqKpJv1wgAUFwo +``` + +*** + +# Step 9: Invoke program via Alchemy RPC + +```ts +import { + Connection, + PublicKey, + Keypair, + Transaction, + TransactionInstruction, +} from "@solana/web3.js"; + +const ALCHEMY_RPC = "https://solana-devnet.g.alchemy.com/v2/YOUR_KEY"; +const PROGRAM_ID = new PublicKey("YOUR_PROGRAM_ID"); + +async function main() { + const connection = new Connection(ALCHEMY_RPC); + + const payer = Keypair.generate(); + await connection.requestAirdrop(payer.publicKey, 1e9); + + const ix = new TransactionInstruction({ + programId: PROGRAM_ID, + keys: [{ pubkey: payer.publicKey, isSigner: true, isWritable: false }], + data: Buffer.alloc(0), + }); + + const tx = new Transaction().add(ix); + const sig = await connection.sendTransaction(tx, [payer]); + + console.log("Transaction signature:", sig); + + const logs = await connection.getTransaction(sig, { + commitment: "finalized", + }); + + console.log("Program logs:", logs?.meta?.logMessages); +} + +main(); +``` + +*** + +# 🎉 Success + +You now have a working Solana program deployed on Solana devnet! + +Check out the next guide on how to: +1. set up a frontend for this program +2. invoke it using Alchemy 🚀 diff --git a/fern/api-reference/solana/send-your-first-request.mdx b/fern/api-reference/solana/send-your-first-request.mdx new file mode 100644 index 000000000..cc1a6c85e --- /dev/null +++ b/fern/api-reference/solana/send-your-first-request.mdx @@ -0,0 +1,68 @@ +--- +title: Send Your First Request +description: How to send your first request on Solana using Alchemy +subtitle: How to send your first request on Solana using Alchemy +slug: docs/send-your-first-request +layout: overview +--- + +## Send Your First Request on Alchemy + +*To use the Solana API you'll need to [create a free Alchemy account](https://dashboard.alchemy.com/signup) first!* + +Let's use [`@solana/web3.js`](https://www.npmjs.com/package/@solana/web3.js) package to set up a `Connection` with Alchemy and get the latest slot on Solana! + + + ```text npm + npm install --save @solana/web3.js + ``` + + ```text yarn + yarn add @solana/web3.js + ``` + + + + ```js + import { Connection } from "@solana/web3.js"; + + const connection = new Connection( + "https://solana-devnet.g.alchemy.com/v2/YOUR_ALCHEMY_API_KEY", + "confirmed" + ); + + const slot = await connection.getSlot(); + console.log("Current slot:", slot); + ``` + + +Now that you've set up a connection to Alchemy, you can continue with some basics: + +## Create a Wallet + + + ```js + import { Keypair } from "@solana/web3.js"; + + const wallet = Keypair.generate(); + console.log("Public Key:", wallet.publicKey.toBase58()); + ``` + + +## Check Wallet Balance + + + ```js + const balance = await connection.getBalance(wallet.publicKey); + console.log("Balance:", balance / 1e9, "SOL"); + ``` + + +## Fetch Account Info + + + ```js + const info = await connection.getAccountInfo(wallet.publicKey); + console.log(info); + ``` + diff --git a/fern/api-reference/solana/solana-api-faq.mdx b/fern/api-reference/solana/solana-api-faq.mdx index 950a1a2f8..3ef1e1bcf 100644 --- a/fern/api-reference/solana/solana-api-faq.mdx +++ b/fern/api-reference/solana/solana-api-faq.mdx @@ -2,26 +2,331 @@ title: Solana API FAQ description: Frequently asked questions about the Solana API subtitle: Frequently asked questions about the Solana API -slug: reference/solana-api-faq +slug: docs/solana-api-faq --- -## What is Solana +## What is Solana? Solana is a high-performance blockchain platform designed to support the scale, open participation, and performance required to power the next generation of decentralized applications. It is built on a new consensus algorithm called Proof of History (PoH) which allows it to process up to 50,000 transactions per second. Solana also features a unique architecture that allows for low-latency transactions, low transaction fees, and a high degree of decentralization. -## What is the Solana API +## What is the Solana API? The Solana API allows decentralized applications to connect to a Solana node that is part of the Solana blockchain. Developers can interact with on-chain data, send transactions, and deploy contracts by leveraging Alchemy's API endpoints. The API follows a JSON-RPC standard, a lightweight, remote procedure call (RPC) protocol that is commonly used when interacting with blockchain data. ## How can I get started using the Solana API? -Explained in the [Solana API Quickstart Guide](/reference/solana-api-quickstart#getting-started-instructions). +Explained in the [Solana API Quickstart Guide](/docs/solana). ## What methods does Alchemy support for the Solana API? You can find the list of all the methods Alchemy support for the Solana API on the [Solana API Endpoints](/reference/solana-api-endpoints) page. -### Methods +## Can I see the Solana methods by category? -* getTokenLargestAccounts -* getTokenAccountsByDelegate +Yes! Check out the section below: + + + + Query live blockchain data and network status + + + + Access complete transaction and block history + + + + Send and simulate transactions with fee estimation + + + + Monitor validators, epochs and network performance + + + +## Current State Methods + +Query live blockchain data including accounts, balances, current slots, and real-time network status. + +### Account & Balance Queries + + + + Returns all information associated with the account of provided Pubkey. + + + + Returns the lamport balance of the account of the provided Pubkey. + + + + Returns the account information for a list of Pubkeys. + + + + Returns all accounts owned by the provided program Pubkey. + + + + Returns the addresses of the largest accounts, by lamport balance. + + + + Returns information about the current supply of SOL. + + + +*** + +### Token Account Methods + + + + Get all token accounts for a wallet. + + + + Query token accounts by delegate. + + + + Get balance of a specific token account. + + + + Query total supply of an SPL token. + + + + Find accounts with largest token holdings. + + + +*** + +### Current Slot & Blockhash + + + + Get current slot number. + + + + Get current block height of the network. + + + + Get most recent blockhash for transactions. + + + + Validate if a blockhash is still valid. + + + + Get current slot leader. + + + + Get slot leaders for a range of slots. + + + +*** + +### Transaction Status & Confirmation + + + + Check confirmation status of transactions. + + + + Get total number of transactions processed. + + + +*** + +## Historical Data (Archival) + +Access complete transaction and block history from Solana genesis. + +### Transaction History + + + + Get detailed information about a specific transaction. + + + + Get transaction signatures for an account. + + + + Calculate inflation rewards for accounts. + + + +*** + +### Block History + +Access blockchain structure, timing, and historical data. + + + + Get complete block information including all transactions. + + + + Get list of confirmed blocks in a range. + + + + Get limited number of confirmed blocks. + + + + Get estimated production time of a block. + + + + Get commitment for a particular block. + + + +*** + +## Transaction Submission + +Send and simulate transactions with fee estimation and optimization. + +### Transaction Methods + + + + Send a transaction to the network. + + + + Simulate a transaction to preview effects. + + + + Request SOL airdrop on Devnet. + + + + Get recent priority fees for optimal pricing. + + + + Calculate transaction fees before sending. + + + +*** + +## Network & Cluster Methods + +Monitor validators, epochs, network performance, and cluster health. + +### Cluster Information + + + + Check RPC node health status. + + + + Get Solana software version information. + + + + Get information about cluster validators. + + + + Get current and delinquent vote accounts. + + + + Get information about the current epoch. + + + + Get epoch schedule information. + + + + Get leader schedule for an epoch. + + + +*** + +### Network Performance & Economics + + + + Get recent network performance metrics. + + + + Get current inflation parameters. + + + + Get current inflation rate. + + + + Get epoch activation information for a stake account. + + + + Get block production information for current/previous epochs. + + + +*** + +## Utility & System Methods + +Helper methods for system information, validation, and advanced queries. + +### Basic Utility Methods + + + + Calculate minimum balance for rent exemption. + + + + Get genesis hash of the cluster. + + + + Get identity public key of the RPC node. + + + + Get slot of first available block. + + + + Get highest slot with a snapshot. + + + + Get minimum slot that node has ledger information. + + + + Get max slot seen from retransmit stage. + + + + Get max slot seen from after shred insert. + + diff --git a/fern/api-reference/solana/solana-api-quickstart.mdx b/fern/api-reference/solana/solana-api-quickstart.mdx index ee44d6180..c7e62d035 100644 --- a/fern/api-reference/solana/solana-api-quickstart.mdx +++ b/fern/api-reference/solana/solana-api-quickstart.mdx @@ -1,160 +1,104 @@ --- -title: Solana API Quickstart -description: Quickstart guide for building on Solana -subtitle: Quickstart guide for building on Solana -slug: reference/solana-api-quickstart +title: Solana API Overview +description: Overview of Alchemy methods for building on Solana +subtitle: Overview of Alchemy methods for building on Solana +slug: docs/solana --- - - Sign up to start building on Solana. [Get started for free](https://alchemy.com/solana/?a=fb3149785d) + + Build faster with production-ready APIs, smart wallets and rollup infrastructure across 70+ chains. Create your free Alchemy API key and{" "} + get started today. -# Getting Started Instructions +## Send Your First Request on Alchemy -## 1. Choose a package manager (npm or yarn) - -For this guide, we will be using npm or yarn as our package manager to install any required packages. - -### npm - -To get started with `npm`, follow the documentation to install Node.js and `npm` for your operating system: [https://docs.npmjs.com/downloading-and-installing-node-js-and-npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) - -### yarn - -To get started with `yarn`, follow these steps: [https://classic.yarnpkg.com/lang/en/docs/install](https://classic.yarnpkg.com/lang/en/docs/install/#mac-stable) - -## 2. Set up your project (npm or yarn) +Let's use [`@solana/web3.js`](https://www.npmjs.com/package/@solana/web3.js) package to set up a `Connection` with Alchemy and get the latest slot on Solana! - ```text Shell (npm) - mkdir alchemy-solana-api - cd alchemy-solana-api - npm init --yes + ```text npm + npm install --save @solana/web3.js ``` - ```text Shell (yarn) - mkdir alchemy-solana-api - cd alchemy-solana-api - yarn init --yes + ```text yarn + yarn add @solana/web3.js ``` -## 3. Install Solana Web3.js Library - -Run the following command to install the Solana Web3.js library with npm or yarn. +## Create Connection to Alchemy - ```text npm - npm install --save @solana/web3.js - ``` + ```js + import { Connection } from "@solana/web3.js"; - ```text yarn - yarn add @solana/web3.js + const connection = new Connection( + "https://solana-devnet.g.alchemy.com/v2/YOUR_ALCHEMY_API_KEY", + "confirmed" + ); + + const slot = await connection.getSlot(); + console.log("Current slot:", slot); ``` -## 4. Make your first request +Now that you've set up a connection to Alchemy, you can continue with some basics: -You are all set now to use Solana API and make your first request. For instance, lets make a request to `get latest slot`. Create an `index.js` file and paste the following code snippet into the file. +## Create a Wallet - ```javascript index.js - const solanaWeb3 = require("@solana/web3.js"); + ```js + import { Keypair } from "@solana/web3.js"; - const main = async () => { - rpc = "https://solana-mainnet.g.alchemy.com/v2/"; // RPC URL for connecting with a Solana node - connection = new solanaWeb3.Connection(rpc, "confirmed"); // confirming the connection + const wallet = Keypair.generate(); + console.log("Public Key:", wallet.publicKey.toBase58()); + ``` + - let slot = await connection.getSlot(); // getting the most recent slot number - console.log("The latest slot number is", slot); // logging the most recent slot number - }; +## Check Wallet Balance - main(); + + ```js + const balance = await connection.getBalance(wallet.publicKey); + console.log("Balance:", balance / 1e9, "SOL"); ``` -## 5. Run script - -To run the above node script, use cmd `node index.js`, and you should see the output. +## Fetch Account Info - ```text shell - The latest slot number is 164150510 + ```js + const info = await connection.getAccountInfo(wallet.publicKey); + console.log(info); ``` -*** - -# Leverage Alchemy's AccountsDB Infrastructure for Solana RPC Requests! +## Find Solana Methods by Category -The most expensive Solana RPC requests involve account scans, such as `getProgramAccounts` and `getLargestTokenAccounts`. These methods are incredibly useful but non-performant methods since they induce a heavy RPC load on Solana validator nodes, often resulting in a 5XX response due to timeout or a response with high latency. + + + Query live blockchain data and network status + -Alchemy has built out core infrastructure that sits atop our Solana validator nodes to support these methods at scale, through what we call the AccountsDB Infrastructure. This infrastructure allows for **faster**, **scalable**, and more **reliable** responses to these methods by paginating the response with a `pageKey`. You could then loop through your that same request with at scan and aggregate the full response from our validator nodes. + + Access complete transaction and block history + -You can see `pageKey` is now an optional parameter in each account-scanning method in our Solana [docs](/reference/getprogramaccounts), and you may also include an `order` optional parameter that would sort the accounts in the response by their `pubkey` field. + + Send and simulate transactions with fee estimation + -Here's an example with `getProgramAccounts`: + + Monitor validators, epochs and network performance + + - - ```typescript typescript - const axios = require("axios"); - - async function getProgramAccountsExample() { - let gPAExampleRequest = { - method: "getProgramAccounts", - params: [ - "ZETAxsqBRek56DhiGXrn75yj2NHU3aYUnxvHXpkf3aD", - { - encoding: "base64", - withContext: true, - order: "desc", - dataSlice: { - offset: 0, - length: 10, - }, - }, - ], - id: 0, - jsonrpc: "2.0", - }; - let programAccounts = []; - - const alchemyRPCUrl = - "https://solana-mainnet.g.alchemy.com/v2/"; - try { - let response = await axios.post(alchemyRPCUrl, gPAExampleRequest); - let responseData = response.data["result"]; - - // continue aggregating if there's a new pageKey present in the latest response - while (responseData["pageKey"]) { - programAccounts = programAccounts.concat(responseData["value"]); - - // place the pagekey within the optional config object - // (you may need to create that config object if you didn't have it originally) - gPAExampleRequest["params"][1]["pageKey"] = responseData["pageKey"]; - - // make another call to getProgramAccounts with the pageKey - response = await axios.post(alchemyRPCUrl, gPAExampleRequest); - responseData = response.data["result"]; - } - - programAccounts = programAccounts.concat(responseData["value"]); - console.log(programAccounts); - return programAccounts; - } catch (err) { - console.log(err.message); - return []; - } - } - ``` - +# CU Calculator -*** +Want to estimate your Solana Compute Units (CUs) before running transactions?\ +**Try the CU Calculator now:** [Open CU Calculator →](https://solana-demo-sigma.vercel.app/) # Solana Tutorials You must not stop here! Check out the following tutorials to learn how to build with Solana: * [Hello World Solana Program](/docs/hello-world-solana-program) -* [How to Integrate a Solana Program with a Web3 Application](/docs/integrating-your-solana-program-with-a-web3-application) -* [How to Build a Solana NFT Collection](/docs/how-to-build-a-solana-nft-collection) +* [How to Integrate a Solana Program with a Web3 Application](/docs/hello-world-solana-application) diff --git a/fern/docs.yml b/fern/docs.yml index cb390a7e7..b114718c4 100644 --- a/fern/docs.yml +++ b/fern/docs.yml @@ -584,11 +584,11 @@ navigation: contents: - section: Hello World Solana Program path: >- - tutorials/creating-smart-contracts/hello-world-solana-program/hello-world-solana-program.mdx + api-reference/solana/hello-world.mdx contents: - page: How to Integrate a Solana Program with a Web3 Application path: >- - tutorials/creating-smart-contracts/hello-world-solana-program/integrating-your-solana-program-with-a-web3-application.mdx + api-reference/solana/hello-world-application.mdx slug: hello-world-solana-program - page: How to Deploy a Smart Contract to the Sepolia Testnet path: >- @@ -1051,6 +1051,28 @@ navigation: api-name: eth-beacon flattened: true slug: ethereum + - section: Solana + contents: + - section: Overview + path: api-reference/solana/solana-api-quickstart.mdx + contents: + - link: CU Calculator + href: https://solana-demo-sigma.vercel.app/ + - page: Accounts DB Infrastructure + path: api-reference/solana/accounts-db-infra.mdx + - section: Solana Tutorials + contents: + - page: Hello World Solana Application + path: api-reference/solana/hello-world.mdx + - page: Set up Frontend for Solana Application + path: api-reference/solana/hello-world-application.mdx + - page: Solana API FAQ + path: api-reference/solana/solana-api-faq.mdx + - api: Solana API Endpoints + api-name: solana + - page: Solana DAS APIs + path: api-reference/data/nft-api/alchemy-das-apis-for-solana.mdx + slug: solana - section: Polygon PoS contents: - section: Polygon PoS API Quickstart @@ -1135,19 +1157,6 @@ navigation: - page: Flashblocks API Quickstart path: api-reference/base/base-flashblocks-api-quickstart.mdx slug: base - - section: Solana - contents: - - page: Solana API Quickstart - path: api-reference/solana/solana-api-quickstart.mdx - # - page: Solana Websockets (Beta) - # path: api-reference/solana/solana-websockets.mdx - # - page: Solana gRPC (Beta) - # path: api-reference/solana/solana-grpc.mdx - - page: Solana API FAQ - path: api-reference/solana/solana-api-faq.mdx - - api: Solana API Endpoints - api-name: solana - slug: solana - section: Astar contents: - page: Astar API Quickstart @@ -2821,6 +2830,9 @@ redirects: - source: /reference/solana-api-endpoints destination: /docs/node/solana/solana-api-endpoints/get-account-info permanent: true + - source: /reference/solana-api-faq + destination: /docs/solana-api-faq + permanent: true - source: /reference/getaccountinfo destination: /docs/node/solana/solana-api-endpoints/get-account-info permanent: true @@ -5151,6 +5163,12 @@ redirects: - source: /alchemy/documentation/apis/solana-api destination: /docs/reference/solana-api-quickstart permanent: true + - source: /docs/reference/solana-api-quickstart + destination: /docs/solana + permanent: true + - source: /docs/solana-api-quickstart + destination: /docs/solana + permanent: true - source: /alchemy/documentation/apis/ethereum-api destination: /docs/reference/ethereum-api-quickstart permanent: true @@ -6606,3 +6624,9 @@ redirects: - source: /reference/tron-api-endpoints destination: /docs/reference/chain-apis-overview#tron-apis permanent: true + - source: /reference/solana-das-api-endpoints + destination: /docs/reference/chain-apis-overview#solana-das-apis + permanent: true + - source: /reference/solana-api-endpoints + destination: /docs/reference/chain-apis-overview#solana-apis + permanent: true diff --git a/fern/tutorials/creating-smart-contracts/hello-world-solana-program/hello-world-solana-program.mdx b/fern/tutorials/creating-smart-contracts/hello-world-solana-program/hello-world-solana-program.mdx deleted file mode 100644 index 5aeca7f5f..000000000 --- a/fern/tutorials/creating-smart-contracts/hello-world-solana-program/hello-world-solana-program.mdx +++ /dev/null @@ -1,400 +0,0 @@ ---- -title: Hello World Solana Program -description: This tutorial walks through writing, building, deploying, and testing your very first Solana Program using Anchor and Alchemy. It’s the best way to introduce yourself to Solana development. -subtitle: This tutorial walks through writing, building, deploying, and testing your very first Solana Program using Anchor and Alchemy. It’s the best way to introduce yourself to Solana development. -slug: docs/hello-world-solana-program ---- - -# 1. Introduction - -Welcome! If you’re looking to deploy your first smart contract and build your first web3 app on Solana, you’ve come to right place. By the end of this tutorial, you’ll have created a Solana program that stores a message that anyone can change, and you’ll have deployed it to the official Solana devnet. And we’re going to get through it together! - -If you have any questions or feedback, please contact us at or open a ticket in the dashboard. - -# 2. Creating Your Project - -Alright, now we’re going to use Anchor to initialize your project workspace in a directory of choice: - - - ```shell shell - anchor init solana-hello-world - cd solana-hello-world - ``` - - -This will create a folder with a few different subfolders and files, which you can view with your favorite code editor like VSCode. The most important ones are: - -* A `programs` folder for your Solana Hello World program. Quick reminder - in Solana, programs are “smart contracts,” and this folder already contains a template we can mess around with. -* A `tests`folder to test our Solana program with Javascript. It also contains a template we can use to create tests with. -* An `Anchor.toml` configuration file that will help you set your program ID, Solana blockchain, and test runs. -* An 'app' folder where we'll add frontend code in the next tutorial: [Integrating Your Solana Program with a Web3 Application](/docs/integrating-your-solana-program-with-a-web3-application)] - -This [git commit](https://github.com/alchemyplatform/solana-hello-world/commit/2957a84b4a0d334da6a2d9127798b31f1df84167?diff=unified) is a checkpoint for you! Your code should match the code in this checkpoint! By now, you should have been able to create your Anchor project and peruse these folders. If so, let’s keep going! - -# 3. Building The Hello World Smart Contract - -## Defining the Account Data - -We’ll be making change step-by-step in `programs/solana-hello-world/src/lib.rs`. First, we’re going to to define the schema for the message we want to display and be editable on our app. This message is going to be data stored in a Solana account. - - - An account stores data in Solana. In reality, everything is an account in Solana - programs, wallets, NFTs, etc. They each have a unique address that can be used by programs to access that data. You can read more [here](https://solanacookbook.com/core-concepts/accounts.html#facts). - - -To do that, Anchor gives us a way to easily define a Solana account in Rust. Let’s add the following code to the bottom of our `lib.rs` file: - - - ```rust rust - #[account] - pub struct Message { - pub author: Pubkey, - pub timestamp: i64, - pub content: String, - } - ``` - - -Let’s walk through each line: - -* `#[account]` is a custom [Rust attribute](https://doc.rust-lang.org/reference/attributes.html) that defines a Solana account. Anchor essentially is telling the Rust compiler this is a Solana account. There’s a lot of detail that Anchor abstracts away from us to be able to do this! Pretty cool 😄! -* `pub struct Message` is a Rust struct that defines the properties of a Message we want our program to interact with. You’ll note we want to store the `content` of the most recent message, the `timestamp` when it was published, and the `author`. - -This [git commit](https://github.com/alchemyplatform/solana-hello-world/commit/9e11bbf79f2f0d56743a9b52d7f79d57e9da29c1?diff=unified) is a checkpoint for you to see what should have changed so far! Don’t worry, we have a few more things to do before we’re ready to use our program. - -## Defining the Instruction Context - -Now that we’ve defined the Message account, we can implement instructions as part our the Solana program. We’re going implement two instructions - one to create the first message, and one to update that message. - - - Programs are smart contracts in Solana. Programs can create, retrieve, update, and delete data but they must access that data on the blockchain through accounts. Programs themselves cannot directly store data - they are stateless. - - -Because Solana programs are stateless, we need to provide relevant context for each of those instructions. To do so, Anchor gives us a way to easily define a context in Rust. - -Let’s add the following code to replace the `Initialize{}` struct in our `lib.rs` file with the `CreateMessage` context: - - - ```rust rust - #[derive(Accounts)] - pub struct CreateMessage<'info> { - #[account(init, payer = author, space = 1000)] - pub message: Account<'info, Message>, - #[account(mut)] - pub author: Signer<'info>, - pub system_program: Program<'info, System>, - } - ``` - - -Let’s walk through each line of this struct: - -* `#[derive(Accounts)]` is a custom Rust attribute that defines the context for an instruction as a Rust struct. Again, Anchor abstracts a lot away so we can develop faster. - -* The `message` property is the actual account that the instruction will create. When we call the instruction, we’re going to pass in a public key to use for this account. - - * The `[account(init, payer = author, space = 1000)]` is an account constraint on this property - it tells Rust that `message` is a Solana account. - * The `init` says that this instruction will create the account, through the System Program. - * The `payer` says that the `author` property is who will pay to initialize and keep the data on Solana. - * The `space` says the big the account data will be in bytes. For sake of simplicity, we’re saying a message can be at most 1000 bytes. Normally we would spend time getting efficient about how much space our data will take up, since it determines how much SOL we have to spend. That is out of the scope of this introductory tutorial, though 🙂. - -* The `author` property is the actual sender of the message. It’s a `Signer` account because we will pass in their signature. It’s has a `mutable` account constraint since we will be modifying their balance of SOL when initializing the message account. - -* The `system_program` is Solana’s System Program that will initialize the `message` account. - -* There’s also the `'info` which is a [Rust lifetime](https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html), or essentially a generic which in this case represents an AccountInfo class - a struct that contains the fields of account, listed here. No need to worry about this deeply yet. - - - The is an executable account that is responsible for creating new accounts, allocating account data, assigning accounts to the programs that own them, and more. It is Solana’s core program. You can read more [here](https://docs.solana.com/developing/runtime-facilities/programs#system-program). - - -Similarly, let’s make context for `UpdateMessage`: - - - ```rust rust - #[derive(Accounts)] - pub struct UpdateMessage<'info> { - #[account(mut)] - pub message: Account<'info, Message>, - #[account(mut)] - pub author: Signer<'info>, - } - ``` - - -For this one, we only need to include `message` and `author`. Note that `message` has a different account constraint - since the instruction using this context will be updating the message, we need to make that account `mutable`. - -This [git commit](https://github.com/alchemyplatform/solana-hello-world/commit/44cc2eabb43bfad44be51d003a0deea2d95fb043) is a checkpoint for you to see what should have changed so far! We have one more step - defining the instructions of our Program that will use this context. - -## Defining the Instructions - -At least, we’ll define two instructions for our Solana program - one to read the message, and one to update the message. Instructions are Rust functions defined within a module with the Rust attribute `#[program]`. The module we created when we initialized Anchor was named the same as our project: `"solana_hello_world"`. Let’s add the following code to replace the `initialize{ctx: Context}` function in our `lib.rs` file with the `create_message` function below: - - - ```rust rust - pub fn create_message(ctx: Context, content: String) -> Result<()> { - let message: &mut Account = &mut ctx.accounts.message; - let author: &Signer = &ctx.accounts.author; - let clock: Clock = Clock::get().unwrap(); - - message.author = *author.key; - message.timestamp = clock.unix_timestamp; - message.content = content; - - Ok(()) - } - ``` - - -Let’s walk through each line: - -* As inputs to the `create_message` function, we take in the Context `ctx` that we defined in the last section for type `CreateMessage`, and `content` we want for store as the message. -* The next line initializes a `message` variable by accessing the account that was created as part of this function call through that `init` account constraint in the context. We want a reference to to that account so we use the `&` as a referencer, and we use `mut`to be able to mutate the data. -* The next line does something similar, initializing the `author` variable as a`Signer` account to save it on the `message` account. -* The next line creates a timestamp `clock` using Solana’s `Clock` system variable. Don’t worry too much about the syntax here, but it can only work if the System Program is provided. -* The next line then saves all three variables as properties to the created `message` account. We dereference the `author.key` to store it as a `Pubkey` property in the message. -* Lastly, `Ok(())`, of the type `ProgramResult`, is the output of an instruction. In Rust, we return `Ok(())` with nothing inside that function, and we don’t need to explicitly say `return` - the last line of a function is used as the return value. - -Similarly, let’s define `update_message`: - - - ```rust rust - pub fn update_message(ctx: Context, content: String) -> Result<()> { - let message: &mut Account = &mut ctx.accounts.message; - let author: &Signer = &ctx.accounts.author; - let clock: Clock = Clock::get().unwrap(); - - message.author = *author.key; - message.timestamp = clock.unix_timestamp; - message.content = content; - - Ok(()) - } - ``` - - -Note that this is identical to the `create_message` function! It just uses a difference context type since we’re not initializing a new message, but rather will be pointing to the account containing the current message our Program will be accessing. - -This [git commit](https://github.com/alchemyplatform/solana-hello-world/commit/54b772782c7b5e02b1e7dfb868e78d2d2d39abb1) is a checkpoint for you to see what should have changed so far! Congratulations! You’ve written your first Solana program. Now all that’s left is to deploy to the devnet and test it. - -You can view the entire `lib.rs` file [here](https://github.com/alchemyplatform/solana-hello-world/blob/main/programs/solana-hello-world/src/lib.rs). If you had any issues with the above steps, copying the contents of this file will get you back on track 😄! - -# 4. Build and Deploy Your Solana Program to the Devnet - -## Build the Program - -Ready to deploy your first ever program đŸ€©? It’s super easy to do with Anchor. First, we need to build our program. Remember to configure your Solana environment with your Alchemy RPC URL: - - - ```shell shell - solana config set --url https://solana-devnet.g.alchemy.com/v2/ - ``` - - -In your terminal, go to the top folder of your project `solana-hello-world` and then write: - - - ```shell shell - anchor build - ``` - - -This command compiles our program and creates a `target` folder in our project which contains an important file for our testing in the next step. Right now, what you should know is the first time you run `Anchor build`, it generates a public and private key. The public key is the unique identifier of the program when - the **program Id**. Grab that program Id and add it to your `lib.rs` file as the `declare_id!` - - - ```rust rust - declare_id!("YOUR-PROGRAM-ID"); - ``` - - -Then, open up your `Anchor.toml` file. Change`[programs.localnet]` to `[programs.devnet]`and then change the `solana_hello_world` program Id to the program Id you received when you built the project, and also under `[provider]` change the `cluster` to `“devnet”`: - - - ```toml toml - [programs.devnet] - solana_hello_world = "YOUR-PROGRAM-ID" - ... - ... - ... - [provider] - cluster = "devnet" - ``` - - -Alright! now run `Anchor build` again just to make sure! Our program should have compiled with no problems - so it’s time to deploy to the devnet! - -## Deploy the Program - -Let’s first request some SOL that we’ll use to deploy our program to the devnet: - - - ```shell shell - solana airdrop 3 - ``` - - -Finally, we can write: - - - ```shell shell - anchor deploy --provider.cluster https://solana-devnet.g.alchemy.com/v2/ - ``` - - - - It’s a flag that overrides the same variable in the `Anchor.toml` file to use the Alchemy RPC URL. Since you might push that file publicly in your Github repository, we don’t want you to reveal your API KEY that’s in the RPC URL - that’s your private connection to Solana that should be kept secret! - - We’re working with the Anchor team to be able to easily deploy with Alchemy directly from the `Anchor.toml` file. Until then, you can manually override that flag using the command above. - - -This should have successfully run if you a see a “Deploy success” response in your terminal! Amazing! You deployed your first Solana program! Woohoo! - -This [git commit](https://github.com/alchemyplatform/solana-hello-world/commit/34d403b736f2da2e7dcf6bf748ade10a9c866897) is a checkpoint for you to see what should have changed so far! Note that my program Id will be different from yours, and that’s totally normal! Before we wrap up, let’s make sure we can successfully call our deployed program with some tests. - -You can view the entire `Anchor.toml` file [here](https://github.com/alchemyplatform/solana-hello-world/blob/main/Anchor.toml). If you had any issues with the above step, copying the contents of this file will get you back on track 😄! - -# 5. Test Your Smart Contract - -## Setting up the Testing Environment - -What’s cool is since we’ve deployed our Solana program to devnet, Anchor will actually be able to run our tests directly on the devnet, not just a local instance of the Solana blockchain! - -Because we’re using the mocha framework in Javascript, all our tests are wrapped in the `describe()` function, where our test suite `"solana-hello-world"` will run a set of tests. Each test is an asynchronous function wrapped in `it()`. - -Quickly before we write the tests let’s look at the imports. At the top of the screen, you’ll see this: - - - ```typescript typescript - import * as anchor from "@project-serum/anchor"; - import { Program } from "@project-serum/anchor"; - import { SolanaHelloWorld } from "../target/types/solana_hello_world"; - ``` - - -The first two imports are from Anchor and we’ll use them to define a provider object, which essentially is our connection to Solana through 1) an RPC provider and 2) a Solana wallet address. Connection + Wallet = Provider! - -As for the third import, remember when we ran `Anchor Build` and it made that `target` file? One of the most useful things in there is the IDL file, which is located at `target/idl/solana_hello_world.json`. We’re going to use it to make well-formatted calls to our deployed Solana program! - - - IDL stands for “Interface Description Language” and is essentially the API structure for our Solana program, defining the structure of how to call that method on our deployed Program. If you’ve ever used ABIs in EVM chains like Ethereum or Polygon, it’s pretty similar. It will be used to feed our client later as we interact with our Solana program through Javascript tests and later a full-fledged website! - - -You’ll see this line right before the tests: - - - ```typescript typescript - const program = anchor.workspace.SolanaHelloWorld as Program; - ``` - - -This uses the IDL to essentially create an object that uses both the IDL and Provider to create a custom Javascript API that completely matches our Solana program! This is a very seamless way to interact with our Solana program on behalf of a wallet without needing to know the underlying API! Namely, we can make calls to `create_message()` instruction in our Rust Solana program by calling `program.rpc.createMessage()` in our Javascript tests, and same for `update_message()` connecting to `program.rpc.updateMessage()`! Super cool 👏! - -Last thing to mention, at the top of the `describe()` function, we should also add the `assert` library, which will be used to compare our expected values with the ones actually returned from the methods we call on our deployed Solana program. Let’s also delete that template test in favor of the ones we’e about to write. - -We now have the pieces we need to write tests as if it were a client making requests to our Solana program through a web3 app. Here’s a [git commit](https://github.com/alchemyplatform/solana-hello-world/commit/4c4ca5b96806ff3bbd163a0c766941024b2a887d?diff=unified) with the latest checkpoint. - -## Writing the Tests - -First, we’ll test if we can create a message. In your `tests/solana-hello-world.ts` file, adding the following test within the `describe()` function: - - - ```typescript typescript - it("Can create a message", async () => { - const message = anchor.web3.Keypair.generate(); - const messageContent = "Hello World!"; - await program.rpc.createMessage(messageContent, { - accounts: { - message: message.publicKey, - author: provider.wallet.publicKey, - systemProgram: anchor.web3.SystemProgram.programId, - }, - signers: [message], - }); - - const messageAccount = await program.account.message.fetch( - message.publicKey - ); - - assert.equal( - messageAccount.author.toBase58(), - provider.wallet.publicKey.toBase58() - ); - assert.equal(messageAccount.content, messageContent); - assert.ok(messageAccount.timestamp); - }); - ``` - - -Let’s walk through line-by-line: - -* First, we generated a `Keypair` consisting of a public and private key, where the public key will be used as the accountId for the `message` account that will be created. We then define the content of the message: “Hello World” 😉! - -* Then, we use the use the `program` we defined earlier to make a call to the `createMessage` instruction on our deployed Solana program. - - * From the context of our `createMessage` instruction, we need to provide three accounts: the `message` to be created, the `author` of the message (which is , and the Solana `systemProgram`. We input them as their public keys (remember account Id and program Id are both just public keys!) - * We also need to provide the `Keypair` for the `message` as a signature. This is because we’re having the account sign to confirm to the System program through this instruction to create the `message` account. We also need the signature from the `author`'s wallet, but Anchor automatically implicitly providers, so we don’t have to! - -* After waiting for the instruction to execute, we then access the `message` account on the devnet by reading it from the Solana program we wrote through its public key. - -* Lastly, we use the `assert` library to confirm that the data we stored in the account - the `author`, the content of the `message`, and the `timestamp` are are as we expect them to be. - -For completeness, we’ll add one more a test to see if we can update the message! Add this below the first test: - - - ```typescript typescript - it("Can create and then update a message", async () => { - const message = anchor.web3.Keypair.generate(); - const messageContent = "Hello World!"; - await program.rpc.createMessage(messageContent, { - accounts: { - message: message.publicKey, - author: provider.wallet.publicKey, - systemProgram: anchor.web3.SystemProgram.programId, - }, - signers: [message], - }); - - const updatedMessageContent = "Solana is cool!"; - await program.rpc.updateMessage(updatedMessageContent, { - accounts: { - message: message.publicKey, - author: provider.wallet.publicKey, - systemProgram: anchor.web3.SystemProgram.programId, - }, - }); - - const messageAccount = await program.account.message.fetch( - message.publicKey - ); - - assert.equal( - messageAccount.author.toBase58(), - provider.wallet.publicKey.toBase58() - ); - assert.notEqual(messageAccount.content, messageContent); - assert.equal(messageAccount.content, updatedMessageContent); - assert.ok(messageAccount.timestamp); - }); - ``` - - -The first half is identical to our first test, creating a `message` account. We then call the `updateMessage` instruction with a new message content, `updatedMessageContent`, and the relevant accounts as context. We don’t need to provide any other signatures besides the `author`'s wallet, which Anchor does automatically since through context that it is a `Signer`. We then use the `assert` library to check that the message was updated! - -Now, all we have to do is run the following to check if these two tests pass! - - - ```shell shell - anchor test --provider.cluster https://solana-devnet.g.alchemy.com/v2/ - ``` - - -If both pass, then we’ve tested our Solana program works! This is the final [git commit](https://github.com/alchemyplatform/solana-hello-world/commit/473cf4f6c8c015839d2b5680141e4b31c76edd17) checkpoint 🎉! - -You can view the entire `solana-hello-world.ts` file [here](https://github.com/alchemyplatform/solana-hello-world/blob/main/tests/solana-hello-world.ts). If you had any issues with the above step, copying the contents of this file will get you back on track 😄! - -# 6. You’re Done! - -Congratulations! You successfully wrote your first Solana program in Rust, deployed it the Solana devnet, and tested it using JavaScript. Again, here’s the Github [repo](https://github.com/alchemyplatform/solana-hello-world/commit/473cf4f6c8c015839d2b5680141e4b31c76edd17) with all the code we wrote together for your to view. Take a step back and admire how far you’ve come! - -You can think of this part of the tutorial as creating the “backend” of your web3 app. Next, we’ll dive into how to use this Solana program with a Phantom Wallet in a frontend application. - -If you have any questions or feedback, please contact us at or open a ticket in the dashboard. diff --git a/fern/tutorials/creating-smart-contracts/hello-world-solana-program/integrating-your-solana-program-with-a-web3-application.mdx b/fern/tutorials/creating-smart-contracts/hello-world-solana-program/integrating-your-solana-program-with-a-web3-application.mdx deleted file mode 100644 index 3e776ff69..000000000 --- a/fern/tutorials/creating-smart-contracts/hello-world-solana-program/integrating-your-solana-program-with-a-web3-application.mdx +++ /dev/null @@ -1,837 +0,0 @@ ---- -title: How to Integrate a Solana Program with a Web3 Application -description: This tutorial walks through creating a frontend application that will read and write data from your very first Solana Program using Phantom and Alchemy. It’s the best way to introduce yourself to Solana development. -subtitle: This tutorial walks through creating a frontend application that will read and write data from your very first Solana Program using Phantom and Alchemy. It’s the best way to introduce yourself to Solana development. -slug: docs/integrating-your-solana-program-with-a-web3-application ---- - -# 1. Introduction - -Congrats on making it this far! You’ve already deployed your Solana program, and now we’ll build a frontend application that will interact with the program to allow you write and update a message you’ll store on the Solana blockchain! By the end of this tutorial, you’ll know how to connect your web3 app to a user’s Phantom Wallet and use your previously deployed Solana program to store a message that anyone can change. And we’re going to get through it together! - -Like before, you can find the finished product [here](https://github.com/alchemyplatform/solana-hello-world) on Github, and the visual below is what you're gonna build:. - -![600](f69082a-ezgif.com-gif-maker_4.gif "ezgif.com-gif-maker (4).gif") - -If you have any questions or feedback, please contact us at or open a ticket in the dashboard. - -# 2. Setup Your Application - -## Creating the Application - -In the [Hello World Solana Program](/docs/hello-world-solana-program) tutorial, we setup our Anchor project named `solana-hello-world`. From the terminal make you’re in that project directory. In that project, you’ll find an empty `app` folder. We will overwrite that empty app folder with a [Next.js](https://nextjs.org/docs) Typescript starter code template that will be the base for our web3 application! - - - ```shell shell - yarn create next-app --typescript app - ``` - - -Now, the `app` folder will have a few different subfolders and files, which you can view with your favorite code editor like VSCode. The most important ones for us are: - -* A `pages` folder that contains the actually application code we are going to write. - - * The `pages/api` folder is where our code that will connect to our Solana program will live. - * The `_app.tsx` and `index.tsx` is where our frontend code will live. - -* A `styles`folder that contains the CSS files for our application. We’ll edit the `Home.module.css` once and then you don’t have to worry about it! - -Next, let’s get into the `app` folder and install the dependencies we'll need for Anchor, Solana, and Phantom: - - - ```shell shell - cd app - yarn add @project-serum/anchor @solana/web3.js @solana/wallet-adapter-react @solana/wallet-adapter-react-ui @solana/wallet-adapter-wallets @solana/wallet-adapter-base - ``` - - -This [git commit](https://github.com/alchemyplatform/solana-hello-world/commit/1ecbc0f47e7464630369880c46d4e4fbbbd1df59) is a checkpoint for you to make sure you’ve successfully created your application! By now, you should have been able to create your Next.js project add the relevant dependency libraries we’ll use later. If so, let’s keep going! - -## Setting Up Your Initial Frontend - -Using your favorite code editor (like VSCode), look at your `app/pages/index.tsx`. It has a lot of boilerplate that we don’t need, so delete all the code and add this to start: - - - ```typescript typescript - import styles from "../styles/Home.module.css"; - - export default function Home() { - return ( -
-
-

- Your First Solana Program with{" "} - Alchemy! -

-
-
- ); - } - ``` -
- -All this is doing is rendering a giant title for your application! Next, look at your `app/styles/Home.module.css` file. Same thing - there’s a lot of boiler plate here. Delete the code and add this: - - - ```css css - .container { - padding: 2rem; - } - - .navbar { - display: flex; - justify-content: flex-end; - width: 100%; - } - - .main { - min-height: 80vh; - padding: 64px 0; - flex: 1; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - } - - .title { - margin: 0; - line-height: 1.15; - font-size: 64px; - text-align: center; - } - - .title a { - color: #0070f3; - } - - .title a:hover, - .title a:focus, - .title a:active { - text-decoration: underline; - border-color: #0070f3; - } - - .message_bar { - display: flex; - justify-content: center; - } - - .message_input { - border: none; - font-size: 16px; - font-weight: 600; - height: 48px; - padding: 0 24px; - border-radius: 4px; - margin: 16px; - text-align: center; - } - - .message_button { - background-color: #0070f3; - border: none; - cursor: pointer; - font-size: 16px; - font-weight: 600; - height: 48px; - padding: 0 24px; - border-radius: 4px; - margin: 16px; - text-align: center; - } - - .card { - margin: 16px; - padding: 24px; - text-align: left; - color: inherit; - border: 1px solid #eaeaea; - border-radius: 10px; - transition: color 0.15s ease, border-color 0.15s ease; - max-width: 600px; - } - - .card h2 { - margin: 0 0 16px 0; - font-size: 24px; - } - - @media (prefers-color-scheme: dark) { - .card { - border-color: #222; - } - } - - .loader_bar { - display: flex; - justify-content: center; - align-items: center; - } - - .loader { - border: 16px solid #f3f3f3; - border-top: 16px solid #0070f3; - border-radius: 50%; - width: 30px; - height: 30px; - animation: spin 2s linear infinite; - margin: 16px; - } - - @keyframes spin { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } - } - ``` - - -Don’t worry too much about CSS! All this is doing is making our application look pretty. We don’t need CSS to use our Solana program and create our web3 application. It just looks nicer 😅. If you’re still curious, you can learn more about it [here](https://www.w3schools.com/html/html_css.asp). - -Awesome! We’re ready to look at our app! You can view your application on `http://localhost:3000/` by running the following from your `app` directory on your terminal: - - - ```shell shell - yarn dev - ``` - - -You should see something like this: - -![2000](36d2664-Setup_UI.png "Setup_UI.png") - -Amazing đŸ€©! You have a working web3 application. We haven’t added anything blockchain specific yet, but we’re about to! Make sure your code lines up with this [git commit](https://github.com/alchemyplatform/solana-hello-world/commit/20d64d7c310487e94c3f04ec10aa66bf78c331e9) checkpoint. Alright, now `CTRL+C` from your terminal to stop your app from running for now. We have some changes to make. - -## Add the Solana Program IDL - -To eventually connect to our Solana program, we’re going to need to add the IDL files that were generated when we ran `anchor build` in the last tutorial. Since you’re still in your `app` folder on your terminal, use this command to add the IDL and types files to our web3 application code to use later: - - - ```shell shell - cp -r ../target/idl ./pages/api/idl - cp -r ../target/types ./pages/api/types - ``` - - -One more [git commit](https://github.com/alchemyplatform/solana-hello-world/commit/85516814bbe151a8b07542bf9f7c4ede2e4473c7) checkpoint here to make sure you’re good to go! You should make sure your web3 application looks as amazing as the screenshot above! Your code should match this exactly - if not, copy and paste from it to make sure you’re up-to-date. Things are about to get interesting 😏 - -# 3. Connect Alchemy And Your Phantom Wallet - -## Adding the Alchemy RPC URL - -Alright! Let’s add a way for users to connect to your application using their wallet. A reminder, most people in the Solana ecosystem use Phantom as their wallet of choice. When a wallet connects to an application, it’s giving the app permission to send transaction on its behalf. To send transactions on behalf of our users, we’ll need our Alchemy RPC URL again. - -We’ll use our Alchemy RPC URL as an environment variable in our Next.js application, so we’ll need to create an `.env.local` file at the top-level `app` folder. Add this line and you’re golden: - - - ```text text - NEXT_PUBLIC_ALCHEMY_RPC_URL="https://solana-devnet.g.alchemy.com/v2/" - ``` - - - - Eventually, you’re going to want to push your code to a Github repository, you don’t want to hardcode private information like your Alchemy RPC URL in your application. Otherwise, someone can find it and then spam your connection if they’re not very nice. So instead, we use `.env.local` to hide your Alchemy RPC URL and its API KEY. And thanks to your `app/.gitignore` file, this specific file `.emv.local` won’t ever be pushed to Github. Problem solved! - - -Here’s a quick [git commit](https://github.com/alchemyplatform/solana-hello-world/commit/726266c37c89461e6acd24c4bdb7a9d4c1802171) checkpoint for you to confirm you did this right! To clarify, I added a `.env.local.example` file, but locally you should have a`.env.local` file (it won’t be tracked by Github). You should also have added your API Key. - -## Adding Constants and Helper Functions - -Now that we set up our Alchemy RPC URL, we need to add some other variables that the rest of our application will use on top of this private environment variable. With `app` as our home directory,, under the `api` folder, let’s make a new folder called `utils` and then create a file called `constants.ts` to add the following: - - - ```typescript typescript - import idl from "../idl/solana_hello_world.json"; - import { Connection, PublicKey, clusterApiUrl } from "@solana/web3.js"; - - /* Constants for RPC Connection the Solana Blockchain */ - export const commitmentLevel = "processed"; - export const endpoint = - process.env.NEXT_PUBLIC_ALCHEMY_RPC_URL || clusterApiUrl("devnet"); - export const connection = new Connection(endpoint, commitmentLevel); - - /* Constants for the Deployed "Hello World" Program */ - export const helloWorldprogramId = new PublicKey(idl.metadata.address); - export const helloWorldprogramInterface = JSON.parse(JSON.stringify(idl)); - ``` - - -Walking through this line-by-line: - -* First, we imported the IDL and then some relevant classes from the Solana web3 library. -* We then create some constants to denote the `commitmentLevel` we’ll look for in our `connection` to the Solana blockchain through our Alchemy RPC URL `endpoint`. -* Lastly, we’ll add constants from the IDL we imported earlier to have easy access to our `helloWorldprogramId` and `helloWorldprogramInterface`. We’ll keep them in the same file, and they’ll be useful when we make calls to our Solana program in the next step. - - - The commitment describes how finalized a block containing transactions is at that point in time. You may know that blockchains are just a chain of bundles of transactions, called blocks. Before being appended to the chain to be read by applications, blocks that require confirmation from nodes in the network, which takes time. The commitment level determines how many nodes in the network need to confirm the block before it’s ready to be read through a client for a web3 application. The more nodes that confirmed, the more likely the block was truly appended to the blockchain. - - Essentially, it’s a tradeoff how fast vs. safe you want your application to be when it comes to reading transactions from Solana, where `processed` is fastest and `finalized` is most safe. Typically, people go in the middle with `confirmed`, but for this application we can use `processed`.You can read more [here](https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment) about this! - - -While we’re in `app/pages/api/utils`, let’s add one more file called `useIsMounted.ts` and this content: - - - ```typescript typescript - import { useEffect, useState } from "react"; - - export default function useIsMounted() { - const [mounted, setMounted] = useState(false); - - useEffect(() => { - setMounted(true); - }, []); - - return mounted; - } - ``` - - -Without this, you’re going to run into a [Hydration Error](https://nextjs.org/docs/messages/react-hydration-error) when integrating your Phantom wallet. This isn’t too important - the main takeaway we need to access the `window.solana` object, which won’t be accessible to our application until after our component mounts. Through the React useEffect hook, we’re able to bypass this! (You can watch [this video](https://youtu.be/bq8nVKxDz5A?t=982) for a similar explanation with a different wallet library if you’re curious!). - -Okay! Make sure your app constants and helper function are looking good - we'll have a git commit checkpoint after we add our Phantom wallet code now😁! - -## Integrating Your Phantom Wallet - -First thing we have to do is go to our Phantom Wallet and adjust the network cluster to Devnet for it to work with our application. Click on the “Settings” button in the top left, then go to “Developer Settings.” Then click on “Change Network” to adjust the network to “Devnet.” Check out my screen recording GIF below: - -![366](baadb9e-ezgif.com-gif-maker_9.gif "ezgif.com-gif-maker (9).gif") - -Now, let’s add some providers to our `app/pages/_app.ts` file to help support integrating a Phantom Wallet. Delete the boilerplate code in there and then add this: - - - ```typescript typescript - import type { AppProps } from "next/app"; - import { PhantomWalletAdapter } from "@solana/wallet-adapter-phantom"; - import { - ConnectionProvider, - WalletProvider, - } from "@solana/wallet-adapter-react"; - import { WalletModalProvider } from "@solana/wallet-adapter-react-ui"; - import { endpoint } from "./api/utils/constants"; - import "@solana/wallet-adapter-react-ui/styles.css"; - import "../styles/globals.css"; - - function MyApp({ Component, pageProps }: AppProps) { - const phantomWallet = new PhantomWalletAdapter(); - - return ( - - - - - - - - ); - } - - export default MyApp; - ``` - - -Let’s walk through each section: - -* The first few lines are just importing the relevant libraries we installed in Step 1 to help with support different kinds of Solana wallets in our application. -* The `MyApp` function is the top-level component that will render our application. We instantiated a variable `phantomWallet` to represent a way to connect to a user’s Phantom wallet in our app. We then render our application’s components. -* A React Provider is just a wrapper around our application code, providing the [context](https://reactjs.org/docs/context.html) of what RPC URL endpoint we are using (Alchemy) and what wallets we want to show (Phantom). to our app from the Solana wallet libraries we installed. There’s a lot of detail that these libraries abstract away from us to be able to integrate a wallet seamlessly! Pretty cool 😄! - -Cool! Now let’s add the Phantom Wallet to `app/pages/index.tsx` by editing it as so: - - - ```typescript typescript - import { WalletMultiButton } from "@solana/wallet-adapter-react-ui"; - import useIsMounted from "./api/utils/useIsMounted"; - import styles from "../styles/Home.module.css"; - - export default function Home() { - const mounted = useIsMounted(); - - return ( -
-
{mounted && }
- -
-

- Your First Solana Program with{" "} - Alchemy! -

-
-
- ); - } - ``` -
- -Some small changes from last time we touched this file: - -* We imported some libraries to help with our wallet button. -* We added the `mounted` function to make sure our wallet button renders only until after the component has mounted, as described earlier. -* We added our wallet button to appear at the top right of our app using the `WalletMultiButton` component! - -At this your app should look like this: - -![600](1c5714a-ezgif.com-gif-maker_6.gif "ezgif.com-gif-maker (6).gif") - -Nice đŸ„ł! We’ve successfully connected a Phantom wallet to your application! Now, you can write code that sends transactions on behalf of the user (with their approval) to write data to the Solana blockchain. Here’s a [git commit](https://github.com/alchemyplatform/solana-hello-world/commit/207f77372e6f88a2b882af571f8b53455b6608b1) checkpoint. Let’s keep going! - -# 4. Connect the App to Your Solana Program - -Now we made it to the cool part - connecting to the Solana program we deployed đŸ˜€! Really quickly, airdrop some SOL to your Wallet since we’re going to need it soon. - - - ```shell shell - solana airdrop 3 - ``` - - -## Making a Create Message API - -Let’s go over what we want our app to do: - -1. When a user successfully connects their wallet, we want to show an input form for a user to write a message. -2. Then, a user should press a button to write that message to the Solana blockchain. -3. Once it’s written, we should display on our application the details of the message, including its content, author (the user), and the time it was published. - -We can actually do all of this by making calls to our Solana program. In our `app/pages/api` folder let’s rename `hello.ts` to `createMessage.ts` and then remove all the code and replace it with this: - - - ```typescript typescript - import { Program, AnchorProvider, web3 } from "@project-serum/anchor"; - import { SolanaHelloWorld } from "./types/solana_hello_world"; - import { - connection, - commitmentLevel, - helloWorldprogramId, - helloWorldprogramInterface, - } from "./utils/constants"; - import { AnchorWallet } from "@solana/wallet-adapter-react"; - - export default async function createMessage( - inputtedMessage: string, - wallet: AnchorWallet, - messageAccount: web3.Keypair - ) { - const provider = new AnchorProvider(connection, wallet, { - preflightCommitment: commitmentLevel, - }); - - if (!provider) return; - - /* create the program interface combining the idl, program Id, and provider */ - const program = new Program( - helloWorldprogramInterface, - helloWorldprogramId, - provider - ) as Program; - - try { - /* interact with the program via rpc */ - const txn = await program.rpc.createMessage(inputtedMessage, { - accounts: { - message: messageAccount.publicKey, - author: provider.wallet.publicKey, - systemProgram: web3.SystemProgram.programId, - }, - signers: [messageAccount], - }); - - const message = await program.account.message.fetch( - messageAccount.publicKey - ); - console.log("messageAccount Data: ", message); - return message; - } catch (err) { - console.log("Transaction error: ", err); - return; - } - } - ``` - - -You’ll notice the code is actually very similar to what we wrote in our tests in the last tutorial! Let’s walk through it briefly: - -* After importing relevant libraries and constants, our function will take in the `inputtedMessage` from the user, the user’s `wallet`, and the `account` that our Program will initialize to save the message in. -* We create a `provider` object, which if you remember from last tutorial, is our connection to Solana through 1) an RPC provider and 2) a Solana wallet address. Connection + Wallet = Provider! We also specify the same commitment level as before. -* Lastly, we make a call to our Solana program to create the Message. Like in our tests in the last tutorial, we include the relevant accounts and signatures needed, along with the `inputtedMessage` to make the call. We then fetch and return that message to use in our frontend! - -Let’s incorporate this new API endpoint in our frontend code now! The full `app/pages/index.tsx` file should look like this now: - - - ```typescript typescript - import { useState } from "react"; - import { Keypair } from "@solana/web3.js"; - import { useAnchorWallet } from "@solana/wallet-adapter-react"; - import { WalletMultiButton } from "@solana/wallet-adapter-react-ui"; - import useIsMounted from "./api/utils/useIsMounted"; - import createMessage from "./api/createMessage"; - import styles from "../styles/Home.module.css"; - - export default function Home() { - const [messageAccount, _] = useState(Keypair.generate()); - const [message, setMessage] = useState(""); - const [messageAuthor, setMessageAuthor] = useState(""); - const [messageTime, setMessageTime] = useState(0); - const [inputtedMessage, setInputtedMessage] = useState(""); - - const wallet = useAnchorWallet(); - const mounted = useIsMounted(); - - return ( -
-
{mounted && }
- -
-

- Your First Solana Program with{" "} - Alchemy! -

- - {wallet && ( -
- setInputtedMessage(e.target.value)} - value={inputtedMessage} - /> - -
- )} - - {wallet && message && ( -
-

Current Message: {message}

-

- Message Author: {messageAuthor.substring(0, 4)} - ... - {messageAuthor.slice(-4)} -

-

Time Published: {new Date(messageTime).toLocaleString()}

-
- )} -
-
- ); - } - ``` -
- -We added a few things - let’s review: - -* We imported more relevant libraries and our newly created `createMessage` function - -* We included a few state variables that will be used. - - * `messageAccount` is the generated public-private keypair that will represent storage on the Solana blockchain for our message. We initialized it with `Keypair.generate()` - * `message`, `messageAuthor`, `messageTime` will store the three corresponding components of a message - it’s content, author, and timestamp. We’ll use this to render a - * `inputtedMessage` will track what the user inputs as a message in the newly created inputted field below until they submit it. When a message is written, we will clear this variable out. - -* We then added an input field and button to our page so our user can input and submit a message if their wallet is connected. - -* Lastly, if there is a message that was submitted and the user’s wallet is still connected, we’ll render the message’s content, author, and date published. - -Now your app should look like this: - -![600](93e2df5-ezgif.com-gif-maker_5.gif "ezgif.com-gif-maker (5).gif") - -Look how far you’ve come 👹‍🎓! You’ve made an app that can connect a user’s wallet and submit to the blockchain a message they write, AND you’re able to show it on your application. So impressive. We’re 99% there - here’s a [git commit](https://github.com/alchemyplatform/solana-hello-world/commit/6393279cd5c95697465c7d641da7950e8f160ef4) checkpoint to make sure your code is all there. - -## Making an Update Message API - -There is one thing left to do. If you try to write a message, and then write another message, you’ll get an error saying the message account was already initialized. We need to call the separate `updateMessage` function on our Solana program to edit the data in the message’s account. - -In our `app/pages/api` folder, let’s add a `updateMessage.ts` file and then add this: - - - ```typescript typescript - import { Program, AnchorProvider, web3 } from "@project-serum/anchor"; - import { SolanaHelloWorld } from "./types/solana_hello_world"; - import { - connection, - commitmentLevel, - helloWorldprogramId, - helloWorldprogramInterface, - } from "./utils/constants"; - import { AnchorWallet } from "@solana/wallet-adapter-react"; - - export default async function updateMessage( - inputtedMessage: string, - wallet: AnchorWallet, - messageAccount: web3.Keypair - ) { - const provider = new AnchorProvider(connection, wallet, { - preflightCommitment: commitmentLevel, - }); - - if (!provider) return; - - /* create the program interface combining the idl, program Id, and provider */ - const program = new Program( - helloWorldprogramInterface, - helloWorldprogramId, - provider - ) as Program; - - try { - /* interact with the program via rpc */ - const txn = await program.rpc.updateMessage(inputtedMessage, { - accounts: { - message: messageAccount.publicKey, - author: provider.wallet.publicKey, - systemProgram: web3.SystemProgram.programId, - }, - }); - - const message = await program.account.message.fetch( - messageAccount.publicKey - ); - console.log("updated messageAccount Data: ", message); - return message; - } catch (err) { - console.log("Transaction error: ", err); - return; - } - } - ``` - - -This is nearly identical to `createMessage`, the only difference is the function we call on our Solana program now is `updateMessage` which doesn’t need the message account’s signature, just the signer (author), which is implicitly added. Everything else is the same! - -Let’s integrate this in our `app/pages/index.tsx` file. The full code should look like this: - - - ```typescript typescript - import { useState } from "react"; - import { Keypair } from "@solana/web3.js"; - import { useAnchorWallet } from "@solana/wallet-adapter-react"; - import { WalletMultiButton } from "@solana/wallet-adapter-react-ui"; - import useIsMounted from "./api/utils/useIsMounted"; - import createMessage from "./api/createMessage"; - import updateMessage from "./api/updateMessage"; - import styles from "../styles/Home.module.css"; - - export default function Home() { - const [messageAccount, _] = useState(Keypair.generate()); - const [message, setMessage] = useState(""); - const [messageAuthor, setMessageAuthor] = useState(""); - const [messageTime, setMessageTime] = useState(0); - const [inputtedMessage, setInputtedMessage] = useState(""); - - const wallet = useAnchorWallet(); - const mounted = useIsMounted(); - - return ( -
-
{mounted && }
- -
-

- Your First Solana Program with{" "} - Alchemy! -

- - {wallet && ( -
- setInputtedMessage(e.target.value)} - value={inputtedMessage} - /> - -
- )} - - {wallet && message && ( -
-

Current Message: {message}

-

- Message Author: {messageAuthor.substring(0, 4)} - ... - {messageAuthor.slice(-4)} -

-

Time Published: {new Date(messageTime).toLocaleString()}

-
- )} -
-
- ); - } - ``` -
- -Let’s walk through the minor changes we made: - -* Now, if there isn’t yet a message published, the button will use the `createMessage` function. If there is a message published, the button will use the `updateMessage` function. - -Your app should now look like this: - -Congrats! You now have the entire web3 app! Users can now write a message to the Solana blockchain, and later edit that same message! Here’s a [git commit](https://github.com/alchemyplatform/solana-hello-world/commit/ec70d6623285ecd8a0f68b3d28a8d4a818e79812) checkpoint to make sure your app is fully function. - -## \[Optional] Prettifying Your Web3 App - -You’ll notice it takes a while to update the UI while the message is being published. To make sure people don’t get confused starting at a screen that isn’t moving, let’s add a loading spinner. This is totally optional, but it’ll make the user experience much better! - -In your `app/pages/index.tsx` let’s edit it to look like this: - - - ```typescript typescript - import { useState } from "react"; - import { Keypair } from "@solana/web3.js"; - import { useAnchorWallet } from "@solana/wallet-adapter-react"; - import { WalletMultiButton } from "@solana/wallet-adapter-react-ui"; - import useIsMounted from "./api/utils/useIsMounted"; - import createMessage from "./api/createMessage"; - import updateMessage from "./api/updateMessage"; - import styles from "../styles/Home.module.css"; - - export default function Home() { - const [messageAccount, _] = useState(Keypair.generate()); - const [message, setMessage] = useState(""); - const [messageAuthor, setMessageAuthor] = useState(""); - const [messageTime, setMessageTime] = useState(0); - const [inputtedMessage, setInputtedMessage] = useState(""); - const [loading, setLoading] = useState(false); - - const wallet = useAnchorWallet(); - const mounted = useIsMounted(); - - return ( -
-
{mounted && }
- -
-

- Your First Solana Program with{" "} - Alchemy! -

- - {wallet && ( -
- setInputtedMessage(e.target.value)} - value={inputtedMessage} - /> - -
- )} - - {loading ? ( -
-

Loading

-
-
- ) : ( - wallet && - message && ( -
-

Current Message: {message}

-

- Message Author: {messageAuthor.substring(0, 4)} - ... - {messageAuthor.slice(-4)} -

-

Time Published: {new Date(messageTime).toLocaleString()}

-
- ) - )} -
-
- ); - } - ``` - - -This was a minor change: - -* We added a state variable to see if our application `isLoading`. -* When a user clicks a button to submit or update a message, it’ll show a loading spinner while the user approves the transaction our app will send on their behalf. -* When we get the message back, we replace the loading spinner with the message details, like what we were showing before! - -You'll see we added the spinner now: - -![600](127061a-ezgif.com-gif-maker_8.gif "ezgif.com-gif-maker (8).gif") - -This is the last [git commit](https://github.com/alchemyplatform/solana-hello-world/commit/68bb071e61ddc99143d1d13f5a9bf85b57724371) checkpoint. You made it to the end đŸ€©! Congratulations! You’ll see the finished product in the next section! - -# 5. You’re Done! - -Hooray 🎉! You made it to the end of the tutorial! To recap, you learned how to: - -1. Connect a Phantom wallet to your web3 app -2. Read data from your Solana program using the Alchemy Web3 API -3. Send Solana transactions for a user through their Phantom Wallet - -Here’s the full code, and this is what the finished product should look like: - -![600](395c84b-ezgif.com-gif-maker_4.gif "ezgif.com-gif-maker (4).gif") - -If you have any questions or feedback, please contact us at or open a ticket in the dashboard.