使用 React、Solana、Anchor 和 Phantom 钱包构建全栈 dapp。
在全栈以太坊开发完全指南中,我深入探讨了如何在以太坊上构建全栈 dapp,这也适用于其他 EVM 兼容链,如 Polygon、Avalanche 和以太坊 Layer 2,比如 Arbitrum。
在本指南中,我想深入研究 Solana,向你展示如何构建全栈 dapp。我还想向你介绍生态系统和开发工具,希望能帮助你开始构建自己的想法和应用程序。
项目的代码位于此处
作为学习过 Solidity 及其生态系统的人,我有点认为要开始并不会太难。我错了。
开发工具的部分确实非常好和成熟(Solana CLI 和 Anchor),而生态系统的其他部分,甚至是 Anchor 的文档(公平地说,这是非常新的),还有相当多的不足之处。
尽管如此,一旦你掌握了一切,很快就会更容易理解如何开始实现自己的想法并开始尝试。
寻找答案的关键之一是要在 Google、Github 以及特别是各种 Discord 服务器中(Anchor 和 Solana)进行仔细搜索。这些频道中的开发人员非常乐于助人,尤其是 Armani Ferrante,他创建了 Anchor 框架。熟悉搜索功能,你通常可以在 Discord 中的过去讨论中找到问题的答案。
我们今天将使用的工具包括:
Solana 工具套件 - 这包括一个与 Solana 网络交互的非常成熟和文档完善的 CLI。
Anchor 框架 - Anchor 对我来说实际上是一个救命稻草,我几乎可以肯定如果没有它,我将无法克服构建任何东西的难关。它是 Solana 开发的 Hardhat,我喜欢它。它还提供了一个 DSL,使你无需深入了解语言即可开始,尽管我仍在努力学习 Rust,因为即使使用 DSL,构建任何有意义的程序都可能需要会用Rust。学习 Rust 的一个很好的免费地方是 Rust 之书 。
solana/web3.js - 一个 Solana 版本的 web3.js,看起来运行得相当不错,但对我来说文档几乎无法使用。
React - 客户端框架
我将不会详细介绍 Solana 本身的工作原理,因为其他人可以比我更好地解释这一点。相反,我将尝试专注于构建一些东西并分享你需要了解的细节,以及我认为最重要的事项。
如果你想了解更多关于 Solana 及其工作原理的信息,这里有一些不错的文章:
在本指南中,我们将主要关注项目设置、测试和前端客户端集成,以构建一些类型的应用程序,主要侧重于 CRUD 操作(当然不包括删除),我发现这方面的文档有些不完整(与客户端应用程序的集成)。
我们还将学习如何使用 Solana CLI 向我们自己的开发账户空投代币,并将我们的应用程序部署到本地网络以及实时测试网络。
在本指南中,我们不会专注于 NFT,但也许我会在未来的指南中专注于这一点。如果你有兴趣在 Solana 上构建 NFT 市场,我建议查看 Metaplex。
本教程涵盖了如何在 Solana 上构建全栈应用程序,但不涉及如何安装所有单独的依赖项。
相反,我将列出依赖项并链接到安装它们的文档,因为每个项目都能更好地解释和记录这些内容,也能保持其更新。
Solana 工具套件 - 你可以查看安装说明此处 。注意 - 如果在 M1 Mac 上安装 Solana 时遇到任何问题,请尝试从源代码构建 ,并查看此指南 。
Solana 浏览器钱包 - 我推荐使用 Phantom,这是我用来测试此应用程序的钱包。
在开始构建之前,让我们看一下 Solana CLI。
我们将使用 Solana CLI 主要进行网络配置(在本地主机和开发者测试网络之间)以及向我们的钱包空投代币,其他几乎所有操作都将使用 Anchor CLI。
例如,我们可以使用以下命令检查当前网络(和其他)配置:
solana config get
# 输出
Config File: /Users/user/.config/solana/cli/config.yml
RPC URL: https://api.devnet.solana.com
WebSocket URL: wss://api.devnet.solana.com/ (computed)
Keypair Path: /Users/user/.config/solana/id.json
Commitment: confirmed
进入全屏模式退出全屏模式
如果你没有
Keypair 路径
,请按照此处的说明设置一个。
我们可以这样更改网络:
# 设置为 localhost
solana config set --url localhost
# 设置为 devnet
solana config set --url devnet
进入全屏模式退出全屏模式
这很重要,因为在构建、测试和部署程序时,你需要注意你正在使用的网络,你的钱包在测试时使用的网络必须与你的本地环境使用的网络相同,这是我将要介绍的内容。
我们将从在localhost
网络上开发开始,然后切换到devnet
网络。我们还可以使用 CLI 查看当前本地钱包地址:
solana address
然后获取关于一个账户的完整详情:
solana account <address from above>
接下来让我们空投一些代币。为此,首先切换到本地网络,因为这是我们将要开始工作的地方:
solana config set --url localhost
接下来,启动本地网络。这将是一个本地的 Solana 节点,我们可以部署用于测试:
solana-test-validator
一旦本地网络运行起来,你可以向你的账户空投代币。在网络运行时,打开一个单独的窗口并运行以下命令:
solana airdrop 100
你可以检查钱包的余额:
solana balance
# 或者
solana balance <address>
你现在应该在你的钱包中有 100 SOL 的余额。有了这个,我们可以开始构建。
要开始,初始化一个新的 anchor 项目并切换到新目录:
anchor init mysolanaapp --javascript
cd mysolanaapp
请确保使用 Anchor 版本 0.16.0 或更高版本。
在这个项目中,你会看到四个主要文件夹(除了node_modules):
app - 我们的前端代码将放在这里
programs - 这是 Solana 程序的 Rust 代码所在的地方
test - 程序的 JavaScript 测试所在的地方
migrations - 一个基本的部署脚本
让我们看一下为我们创建的程序。
Anchor 使用并使我们能够编写一个 eDSL( 嵌入式 DSL),它将许多你通常需要做的更复杂的低级操作抽象出来,如果你没有使用它,你将需要使用 Solana 和 Rust 进行更多的操作,这使得它对我来说更容易接近。
// programs/src/lib.rs
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
pub mod mysolanaapp {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> ProgramResult {
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize {}
这可能是你可以编写的最基本的程序。这里唯一发生的事情是我们定义了一个名为initialize
的函数,当调用时,程序会成功退出。这里根本没有数据操作。
Initialize
结构定义了上下文为空,没有任何参数。我们将在后面学习更多关于函数上下文的知识。
要编译这个程序,我们可以运行 Anchor 的build
命令:
anchor build
一旦构建完成,你应该会看到一个名为target的新文件夹。
创建的工件之一是位于target/idl/mysolanaapp.json的 IDL。
IDL 与 Solidity 中的 ABI(或 GraphQL 中的查询定义)非常相似,我们将在 JavaScript 测试和前端中以类似的方式使用它们来通过 RPC 与我们的 Solana 程序通信。
我们也可以测试我们的程序。如果你打开tests/mysolanaapp.js,你会看到其中有一个用 JavaScript 编写的测试,让我们可以测试程序。
测试应该如下所示:
const anchor = require('@project-serum/anchor');
describe('mysolanaapp', () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
it('Is initialized!', async () => {
const program = anchor.workspace.Mysolanaapp;
const tx = await program.rpc.initialize();
console.log("Your transaction signature", tx);
});
});
从这个测试中,有几个重要的东西可以学习,我们将在未来的测试和前端 JavaScript 客户端中使用,这些对我们很重要。
要使用 Anchor 调用 Solana 程序,通常需要两个主要的东西:
1. Provider
- Provider
是到 Solana 网络的连接的抽象,通常由 Connection
、钱包和预提交承诺组成。
在测试中,Anchor 框架将根据环境(anchor.Provider.env()
)为我们创建提供程序,但在客户端中,我们将需要使用用户的 Solana 钱包构建提供程序。
2. program
- program
是一个结合了Provider
、idl
和programID
(在构建程序时生成)的抽象,允许我们对我们的程序调用RPC
方法。
与Provider
一样,Anchor 提供了一种方便的方法来访问program
,但在构建前端时,我们需要自己构建这个provider
。
一旦我们有了这两个东西,我们就可以开始调用程序中的函数。例如,在我们的程序中,我们有一个initialize
函数。在我们的测试中,你会看到我们可以直接使用program.rpc.functionName
来调用该函数:
const tx = await program.rpc.initialize();
这是你在使用 Anchor 时经常会使用的一种常见模式,一旦你掌握了它的工作原理,就可以很容易地连接到 Solana 程序并与之交互。
我们现在可以通过运行test
脚本来测试程序:
anchor test
现在我们已经设置好项目,让我们创建一些更有趣的东西。
我知道,作为一个全栈开发人员,大多数时候我在想如何进行 CRUD 类型的操作,所以我们接下来将看看这个。
我们将创建的第一个程序将允许我们创建一个计数器,每次我们从客户端应用程序调用它时都会递增。
我们需要做的第一件事是打开programs/mysolanaapp/src/lib.rs并使用以下代码进行更新:
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
mod mysolanaapp {
use super::*;
pub fn create(ctx: Context<Create>) -> ProgramResult {
let base_account = &mut ctx.accounts.base_account;
base_account.count = 0;
Ok(())
}
pub fn increment(ctx: Context<Increment>) -> ProgramResult {
let base_account = &mut ctx.accounts.base_account;
base_account.count += 1;
Ok(())
}
}
// Transaction instructions
#[derive(Accounts)]
pub struct Create<'info> {
#[account(init, payer = user, space = 16 + 16)]
pub base_account: Account<'info, BaseAccount>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
``````rust
// 交易指令
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut)]
pub base_account: Account<'info, BaseAccount>,
}
// 一个包含在交易指令中的账户
#[account]
pub struct BaseAccount {
pub count: u64,
}
在这个程序中,我们有两个函数 - create
和 increment
。这两个函数是我们将能够从客户端应用程序调用的 RPC 请求处理程序,以与程序进行交互。
RPC 处理程序的第一个参数是 Context 结构,描述了在调用函数时将传递的上下文以及如何处理它。在 Create
的情况下,我们期望三个参数:base_account
、user
和 system_program
。
#[account(...)]
属性定义了与之前声明的账户相关的约束和指令。如果这些约束中的任何一个不成立,那么指令将永远不会执行。
任何使用正确的 base_account
调用此程序的客户端都可以调用这些 RPC 方法。
Solana 处理数据的方式与我以往接触过的任何东西都不同。程序内部没有持久状态,一切都附加在所谓的账户上。账户基本上包含程序的所有状态。因此,所有数据都是通过引用从外部传递的。
也没有读取操作。这是因为要读取程序的内容,你只需要请求账户,然后你就能查看程序的所有状态。要了解有关账户如何工作的更多信息,请查看此文章 。
构建程序:
anchor build
接下来,让我们编写一个使用此计数器程序的测试。为此,请打开 tests/mysolanaapp.js 并更新为以下代码:
const assert = require("assert");
const anchor = require("@project-serum/anchor");
const { SystemProgram } = anchor.web3;
describe("mysolanaapp", () => {
/* 创建并设置提供程序 */
const provider = anchor.Provider.env();
anchor.setProvider(provider);
const program = anchor.workspace.Mysolanaapp;
it("Creates a counter)", async () => {
/* 通过 RPC 调用 create 函数 */
const baseAccount = anchor.web3.Keypair.generate();
await program.rpc.create({
accounts: {
baseAccount: baseAccount.publicKey,
user: provider.wallet.publicKey,
systemProgram: SystemProgram.programId,
},
signers: [baseAccount],
});
/* 获取账户并检查 count 的值 */
const account = await program.account.baseAccount.fetch(baseAccount.publicKey);
console.log('Count 0: ', account.count.toString())
assert.ok(account.count.toString() == 0);
_baseAccount = baseAccount;
});
it("Increments the counter", async () => {
const baseAccount = _baseAccount;
await program.rpc.increment({
accounts: {
baseAccount: baseAccount.publicKey,
},
});
const account = await program.account.baseAccount.fetch(baseAccount.publicKey);
console.log('Count 1: ', account.count.toString())
assert.ok(account.count.toString() == 1);
});
});
在继续测试和部署程序之前,我们要获取由构建生成的动态生成的程序 ID。我们需要此 ID 用于在 Rust 程序中使用,以替换我们在创建项目时设置的占位符 ID。要获取此 ID,可以运行以下命令:
solana address -k target/deploy/mysolanaapp-keypair.json
现在,我们可以在 lib.rs 中更新程序 ID:
// mysolanaapp/src/lib.rs
declare_id!("your-program-id");
以及在 Anchor.toml 中:
# Anchor.toml
[programs.localnet]
mysolanaapp = "your-program-id"
接下来,运行测试:
anchor test
一旦测试通过,我们现在可以部署。
我们现在可以部署程序。确保 solana-test-validator
正在运行:
anchor deploy
你还可以通过打开一个单独的窗口并运行
solana logs
来查看验证器日志
现在我们准备构建前端。
在 Anchor 项目的根目录中,创建一个新的 react 应用程序,以覆盖现有的 app 目录:
npx create-react-app app
接下来,安装我们将需要用于 Anchor 和 Solana Web3 的依赖项:
cd app
npm install @project-serum/anchor @solana/web3.js
我们还将使用 Solana Wallet Adapter 来处理连接用户 Solana 钱包。让我们也安装这些依赖项:
npm install @solana/wallet-adapter-react \
@solana/wallet-adapter-react-ui @solana/wallet-adapter-wallets \
@solana/wallet-adapter-base
接下来,在 src 目录中,创建一个名为 idl.json 的新文件。在这里,复制在主项目文件夹中为你创建的 IDL JSON,位于 target/idl/mysolanaapp.json。
如果我们可以自动将此 idl 文件复制到我们的客户端应用程序 src 文件夹中,那将是很好的,但目前我还没有找到原生方法来实现这一点。当然,如果你愿意,你可以创建自己的脚本来执行此操作,否则你需要在每次更改主程序后复制并粘贴 IDL。
如果你想要这样的脚本,你可以在几行代码中完成:
// copyIdl.js
const fs = require('fs');
const idl = require('./target/idl/mysolanaapp.json');
fs.writeFileSync('./app/src/idl.json', JSON.stringify(idl));
接下来,打开 app/src/App.js 并更新为以下内容:
import './App.css';
import { useState } from 'react';
import { Connection, PublicKey } from '@solana/web3.js';
import {
Program, Provider, web3
} from '@project-serum/anchor';
import idl from './idl.json';
import { PhantomWalletAdapter } from '@solana/wallet-adapter-wallets';
import { useWallet, WalletProvider, ConnectionProvider } from '@solana/wallet-adapter-react';
import { WalletModalProvider, WalletMultiButton } from '@solana/wallet-adapter-react-ui';
require('@solana/wallet-adapter-react-ui/styles.css');
const wallets = [
/* 查看可用钱包列表 https://github.com/solana-labs/wallet-adapter#wallets */
new PhantomWalletAdapter()
]
const { SystemProgram, Keypair } = web3;
/* 创建一个账户 */
const baseAccount = Keypair.generate();
const opts = {
preflightCommitment: "processed"
}
const programID = new PublicKey(idl.metadata.address);
``````javascript
function App() {
const [value, setValue] = useState(null);
const wallet = useWallet();
async function getProvider() {
/* 创建提供程序并将其返回给调用者 */
/* 网络暂时设置为本地网络 */
const network = "http://127.0.0.1:8899";
const connection = new Connection(network, opts.preflightCommitment);
const provider = new Provider(
connection, wallet, opts.preflightCommitment,
);
return provider;
}
async function createCounter() {
const provider = await getProvider()
/* 创建包含 idl、程序 ID 和提供程序的程序接口 */
const program = new Program(idl, programID, provider);
try {
/* 通过 rpc 与程序交互 */
await program.rpc.create({
accounts: {
baseAccount: baseAccount.publicKey,
user: provider.wallet.publicKey,
systemProgram: SystemProgram.programId,
},
signers: [baseAccount]
});
const account = await program.account.baseAccount.fetch(baseAccount.publicKey);
console.log('account: ', account);
setValue(account.count.toString());
} catch (err) {
console.log("Transaction error: ", err);
}
}
async function increment() {
const provider = await getProvider();
const program = new Program(idl, programID, provider);
await program.rpc.increment({
accounts: {
baseAccount: baseAccount.publicKey
}
});
const account = await program.account.baseAccount.fetch(baseAccount.publicKey);
console.log('account: ', account);
setValue(account.count.toString());
}
if (!wallet.connected) {
/* 如果用户的钱包未连接,则显示连接钱包按钮。 */
return (
<div style={{ display: 'flex', justifyContent: 'center', marginTop:'100px' }}>
<WalletMultiButton />
</div>
)
} else {
return (
<div className="App">
<div>
{
!value && (<button onClick={createCounter}>Create counter</button>)
}
{
value && <button onClick={increment}>Increment counter</button>
}
{
value && value >= Number(0) ? (
<h2>{value}</h2>
) : (
<h3>Please create the counter.</h3>
)
}
</div>
</div>
);
}
}
/* 钱包配置如下: https://github.com/solana-labs/wallet-adapter#setup */
const AppWithProvider = () => (
<ConnectionProvider endpoint="http://127.0.0.1:8899">
<WalletProvider wallets={wallets} autoConnect>
<WalletModalProvider>
<App />
</WalletModalProvider>
</WalletProvider>
</ConnectionProvider>
)
export default AppWithProvider;
在我们可以与 localhost
网络上的程序交互之前,我们必须将我们的 Phantom 钱包切换到正确的网络。
要这样做,请打开你的 Phantom 钱包并单击设置按钮。然后向下滚动到 更改网络:
接下来,选择 Localhost:
现在我们需要向这个钱包空投代币。在钱包界面顶部,单击你的地址以将其复制到剪贴板。
接下来,打开你的终端并运行以下命令(确保 solana-test-validator
正在运行):
solana airdrop 10 <address>
你现在应该在你的钱包中有 10 个代币。现在,我们可以运行并测试该应用程序!
切换到 app 目录并运行以下命令:
npm start
你应该能够连接你的钱包,创建一个计数器并对其进行递增。
你会注意到,当你刷新页面时,你会丢失程序的状态。这是因为我们在程序加载时动态生成基本帐户。如果你想要在各个客户端之间读取和交互程序数据,你需要在项目中创建并存储 Keypair。我已经整理了一个代码片段 ,展示了这可能是什么样子。
让我们创建这个程序的一个变体,而不是处理计数器,它允许我们创建一条消息并跟踪所有先前创建的消息。
为此,让我们更新我们的 Rust 程序如下:
/* programs/mysolanaapp/src/lib.rs */
use anchor_lang::prelude::*;
declare_id!("your-program-id");
#[program]
mod mysolanaapp {
use super::*;
pub fn initialize(ctx: Context<Initialize>, data: String) -> ProgramResult {
let base_account = &mut ctx.accounts.base_account;
let copy = data.clone();
base_account.data = data;
base_account.data_list.push(copy);
Ok(())
}
pub fn update(ctx: Context<Update>, data: String) -> ProgramResult {
let base_account = &mut ctx.accounts.base_account;
let copy = data.clone();
base_account.data = data;
base_account.data_list.push(copy);
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = user, space = 64 + 64)]
pub base_account: Account<'info, BaseAccount>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Update<'info> {
#[account(mut)]
pub base_account: Account<'info, BaseAccount>,
}
#[account]
pub struct BaseAccount {
pub data: String,
pub data_list: Vec<String>,
}
在这个程序中,我们要跟踪的两个主要数据是一个名为 data
的字符串和一个保存程序中添加的所有数据列表的向量,名为 data_list
。
你会注意到这里的内存分配比之前的程序要高 (128 + 128
),以便考虑到向量。我不知道你能够在这个程序中存储多少更新,但可能值得进一步调查或尝试,因为这个示例本身是实验性的,只是为了让你了解事物是如何工作的。
接下来,我们可以更新这个新程序的测试:
const assert = require("assert");
const anchor = require("@project-serum/anchor");
const { SystemProgram } = anchor.web3;
describe("Mysolanaapp", () => {
const provider = anchor.Provider.env();
anchor.setProvider(provider);
const program = anchor.workspace.Mysolanaapp;
it("It initializes the account", async () => {
const baseAccount = anchor.web3.Keypair.generate();
await program.rpc.initialize("Hello World", {
accounts: {
baseAccount: baseAccount.publicKey,
user: provider.wallet.publicKey,
systemProgram: SystemProgram.programId,
},
signers: [baseAccount],
});
const account = await program.account.baseAccount.fetch(baseAccount.publicKey);
console.log('Data: ', account.data);
assert.ok(account.data === "Hello World");
_baseAccount = baseAccount;
});
it("Updates a previously created account", async () => {
const baseAccount = _baseAccount;
await program.rpc.update("Some new data", {
accounts: {
baseAccount: baseAccount.publicKey,
},
});
const account = await program.account.baseAccount.fetch(baseAccount.publicKey);
console.log('Updated data: ', account.data)
assert.ok(account.data === "Some new data");
console.log('all account data:', account)
console.log('All data: ', account.dataList);
assert.ok(account.dataList.length === 2);
});
});
要进行测试:
anchor test
如果测试失败,请尝试关闭验证器,然后再次运行。
接下来,让我们更新客户端。
/* app/src/App.js */
import './App.css';
import { useState } from 'react';
import { Connection, PublicKey } from '@solana/web3.js';
import { Program, Provider, web3 } from '@project-serum/anchor';
import idl from './idl.json';
import { PhantomWalletAdapter } from '@solana/wallet-adapter-wallets';
import { useWallet, WalletProvider, ConnectionProvider } from '@solana/wallet-adapter-react';
import { WalletModalProvider, WalletMultiButton } from '@solana/wallet-adapter-react-ui';
require('@solana/wallet-adapter-react-ui/styles.css');
const wallets = [ new PhantomWalletAdapter() ]
const { SystemProgram, Keypair } = web3;
const baseAccount = Keypair.generate();
const opts = {
preflightCommitment: "processed"
}
const programID = new PublicKey(idl.metadata.address);
function App() {
const [value, setValue] = useState('');
const [dataList, setDataList] = useState([]);
const [input, setInput] = useState('');
const wallet = useWallet()
async function getProvider() {
/* 创建提供程序并将其返回给调用者 */
/* 网络暂时设置为本地网络 */
const network = "http://127.0.0.1:8899";
const connection = new Connection(network, opts.preflightCommitment);
const provider = new Provider(
connection, wallet, opts.preflightCommitment,
);
return provider;
}
async function initialize() {
const provider = await getProvider();
/* 创建将 idl、程序 ID 和提供程序结合在一起的程序接口 */
const program = new Program(idl, programID, provider);
try {
/* 通过 rpc 与程序交互 */
await program.rpc.initialize("Hello World", {
accounts: {
baseAccount: baseAccount.publicKey,
user: provider.wallet.publicKey,
systemProgram: SystemProgram.programId,
},
signers: [baseAccount]
});
const account = await program.account.baseAccount.fetch(baseAccount.publicKey);
console.log('account: ', account);
setValue(account.data.toString());
setDataList(account.dataList);
} catch (err) {
console.log("Transaction error: ", err);
}
}
async function update() {
if (!input) return
const provider = await getProvider();
const program = new Program(idl, programID, provider);
await program.rpc.update(input, {
accounts: {
baseAccount: baseAccount.publicKey
}
});
const account = await program.account.baseAccount.fetch(baseAccount.publicKey);
console.log('account: ', account);
setValue(account.data.toString());
setDataList(account.dataList);
setInput('');
}
if (!wallet.connected) {
return (
<div style={{ display: 'flex', justifyContent: 'center', marginTop:'100px' }}>
<WalletMultiButton />
</div>
)
} else {
return (
<div className="App">
<div>
{
!value && (<button onClick={initialize}>Initialize</button>)
}
{
value ? (
<div>
<h2>Current value: {value}</h2>
<input
placeholder="Add new data"
onChange={e => setInput(e.target.value)}
value={input}
/>
<button onClick={update}>Add data</button>
</div>
) : (
<h3>Please Inialize.</h3>
)
}
{
dataList.map((d, i) => <h4 key={i}>{d}</h4>)
}
</div>
</div>
);
}
}
const AppWithProvider = () => (
<ConnectionProvider endpoint="http://127.0.0.1:8899">
<WalletProvider wallets={wallets} autoConnect>
<WalletModalProvider>
<App />
</WalletModalProvider>
</WalletProvider>
</ConnectionProvider>
)
export default AppWithProvider;
接下来,构建并部署程序(确保 solana-test-validator
正在运行):
anchor build
anchor deploy
有了新的构建,你将拥有一个新的 IDL,你需要更新客户端。要么将新的 IDL 复制到 app/src/idl.json,要么运行你的 copyIdl.js 脚本。
在测试新程序时,请确保更新由构建创建的idl.json文件。
切换到app目录并运行start
命令:
npm start
从这里开始,向实时网络部署非常简单。我们需要做的主要事情是:
1. 更新 Solana CLI 以使用devnet
:
solana config set --url devnet
2. 更新 Phantom 钱包以使用devnet
3. 打开Anchor.toml并将集群从localnet
更新为devnet
。
4. 重新构建程序。确保Anchor.toml中的程序 ID 与当前程序 ID 匹配。
5. 再次部署程序,这次将部署到devnet
6. 在app/src/App.js中,我们还需要更新网络,这次使用@solana/web3
中的clusterApiUrl
,如下所示:
/* before */
<ConnectionProvider endpoint="http://127.0.0.1:8899">
/* after */
import {
...,
clusterApiUrl
} from '@solana/web3.js';
const network = clusterApiUrl('devnet');
<ConnectionProvider endpoint={network}>
从这里开始,你应该能够像我们之前所做的那样部署和测试。
该项目的代码位于这里
我建议你查看的另一份深入教程是从头开始创建 Solana dApp,该教程实现了 Twitter 的简化版本作为 Solana dapp。
如果你有兴趣全职从事这样的技术工作,请加入我和我的团队 Edge & Node,我们正在招聘!
本文由 AI 翻译,欢迎小伙伴们来校对。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!