BCSkill (Block chain skill )
区块链中文技术社区

只讨论区块链底层技术
遵守一切相关法律政策!

Solana基础 - 开始 Solana - 安装与故障排除

本文详细介绍了如何安装 Solana 开发环境,并提供了一个从安装到运行“Hello World”程序的完整教程,包含问题排查和常见错误的解决方案。
这是一个 Solana 的 Hello World 教程。我们将指导你完成安装 Solana 的步骤并解决可能出现的问题。

如果你遇到问题,请查看本文末尾的故障排除部分。

安装步骤

安装 Rust

如果你已安装 Rust,请跳过此步骤。

# 安装 rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

安装 Yarn

你将需要它来运行单元测试。如果你已安装 yarn,请跳过此步骤。

# 安装 yarn -- 假设已安装 node js
corepack enable # corepack 随 node js 一起提供

安装 Solana cli

我们强烈建议使用 stable 版本,而不是 latest。Solana 的安装不再支持符号通道名称(edgebetastable),因此我们必须指定版本。

# 安装 solana
sh -c "$(curl -sSfL https://release.solana.com/v1.16.25/install)"

安装 Anchor

Anchor 是 Solana 开发的框架。在很多方面,它与 hardhat 非常相似。

# 安装 anchor
cargo install --git https://github.com/coral-xyz/anchor avm --locked --force

avm install latest
avm use latest

测试安装

初始化并构建 Anchor 程序(用于 hello world)

Mac 用户: 我们建议将你的程序命名为 day_1 而不是 day1,因为在 mac 机器上,Anchor 有时会默默插入下划线。

anchor init day1 # 如果你使用的是 mac,请使用 day_1
cd day1
anchor build

根据你的机器和互联网连接,这一步可能需要一段时间。这一步也是你最有可能遇到安装问题的地方,因此如果有必要,请查看故障排除部分。

配置 Solana 以在localhost上运行

# shell 1

solana config set --url localhost

运行测试验证节点

在新的 shell 中运行以下命令,而不是在 Anchor 项目中。但不要关闭你运行 anchor build 的 shell。这是在你的机器上运行本地(测试)Solana 节点实例:

# shell 2

solana-test-validator

确保 program_id 与 Anchor 密钥同步

返回到 Anchor 项目的 shell,运行以下命令:

# shell 1

anchor keys sync

运行测试

在 Anchor 项目中运行:

# shell 1

anchor test --skip-local-validator

上面的命令为我们的程序运行测试。如果你尚未创建测试钱包,Anchor 会给你提供如何创建钱包的说明。我们在这里不提供这些说明,因为它将依赖于你的操作系统和文件结构。你还可能需要通过在终端运行 solana airdrop 100 {YOUR_WALLET_ADDRESS} 来为自己空投一些本地 Sol。你可以通过在命令行中运行 solana address 获取你的钱包地址。

预期输出如下:

Successful test run

Hello World

现在让我们让程序输出“Hello, world!”将以下行标记为 ****NEW LINE HERE**** 添加到 programs/day_1/src/lib.rs

use anchor_lang::prelude::*;

declare_id!("...");

#[program]
pub mod day_1 {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        msg!("Hello, world!"); // **** NEW LINE HERE ****
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize {}

在再次运行测试之前,请结束本地验证器进程并使用以下命令重新启动:

solana-test-validator --reset

再次运行测试

anchor test --skip-local-validator

通过运行找到日志文件

ls .anchor/program-logs/

打开该文件以查看记录的“Hello world”

Program log "Hello world"

实时 Solana 日志

另外,你可以通过打开第三个 shell 并运行以下命令来查看实时日志:

# shell 3

solana logs

现在再次运行测试,你应该会在运行 solana logs 的终端中看到相同的信息。

问题与解答

为什么 declare_id! 和 msg! 后面有感叹号?

在 Rust 中,感叹号表示这些是宏。我们将在以后的教程中再次讨论宏。

我需要 initialize 函数吗?

不,这个是 Anchor 框架自动生成的。你可以将其命名为其他任何名称。

在这种情况下,initialize 这个名称没有什么特别之处,因此我们可以将其更改为我们喜欢的任何名称。这与某些其他关键词和语言不同,比如在某些语言中,main 是一个特殊名称,或者在 Solidity 中构造函数是一个特殊名称。

练习尝试将 programs/day_1/src/lib.rstests/day_1.ts 中的 initialize 重命名为 initialize2 并再次运行测试。见以下橙色圆圈标记的变化。
Renaming excersise

为什么我们使用 --skip-local-validator 运行测试?

当测试针对节点运行时,我们将能够查询节点的状态更改。如果你无法使节点运行,可以不带 --skip-local-validator 标志直接运行 anchor test。但是,你将在开发和测试时遇到更多困难,因此我们建议使本地验证器正常工作。

故障排除

Solana 是一个快速发展的软件,你可能会遇到安装问题。我们已经记录了你最可能遇到的问题,以下是相关部分。

我们的教程系列是用以下版本编写的: Anchor = 版本 0.29.0 Solana = 版本 1.16.25 * Rustc = 1.77.0-nightly

你可以通过运行以下命令更改 Anchor 版本:

avm install 0.29.0
avm use 0.29.0

error: package solana-program v1.18.0 cannot be built

error: package `solana-program v1.18.0` cannot be built because it requires rustc 1.72.0 or newer, while the currently active rustc version is 1.68.0-dev
Either upgrade to rustc 1.72.0 or newer, or use
cargo update -p solana-program@1.18.0 --precise ver

使用 solana --version 检查你当前使用的 Solana 版本。然后将该版本插入 ver 上面。下面显示了一个解决方案示例:

Check Solana version

error[E0658]: use of unstable library feature ‘build_hasher_simple_hash_one’

如果你收到以下错误:

error[E0658]: use of unstable library feature 'build_hasher_simple_hash_one'
--> src/random_state.rs:463:5
|
463 | / fn hash_one<T: Hash>(&self, x: T) -> u64 {
464 | | RandomState::hash_one(self, x)
465 | | }
| |_____^
|
= note: see issue #86161 https://github.com/rust-lang/rust/issues/86161 for more information
= help: add #![feature(build_hasher_simple_hash_one)] to the crate attributes to enable

运行以下命令:cargo update -p ahash@0.8.7 --precise 0.8.6

来源 : https://solana.stackexchange.com/questions/8800/cant-build-hello-world

错误:部署程序失败:处理指令 1 时出错:自定义程序错误:0x1

Error: Deploying program failed: Error processing Instruction 1: custom program error: 0x1
There was a problem deploying: Output { status: ExitStatus(unix_wait_status(256)), stdout: "", stderr: "" }.

如果你收到此错误,说明你的密钥未同步。运行 anchor keys sync

错误:发送交易失败:交易模拟失败:尝试加载不存在的程序

你的密钥未同步。运行 anchor keys sync

错误:你配置的 rpc 端口:8899 已在使用中

你在验证器在后台运行时,运行了不带 --skip-local-validatoranchor test。请关闭验证器并运行 anchor test,或在运行验证器的情况下运行 anchor test --skip-local-validator。跳过本地验证器意味着跳过为项目创建的临时验证器,而不是在后台运行的那个。

错误:账户 J7t…zjK 资金不足

运行下面的命令向你的开发地址空投 100 SOL:

solana airdrop 100 J7t...zjK

错误:RPC 请求错误:集群版本查询失败

Error: RPC request error: cluster version query failed: error sending request for url (http://localhost:8899/): error trying to connect: tcp connect error: Connection refused (os error 61)
There was a problem deploying: Output { status: ExitStatus(unix_wait_status(256)), stdout: "", stderr: "" }.

这意味着 solana-test-validator 并未在后台运行。在另一个 shell 中运行 solana-test-validator

线程 'main' 在 '调用 Option::unwrap() 时' 发生恐慌:None

thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', /Users/username/.cargo/git/checkouts/anchor-50c4b9c8b5e0501f/347c225/lang/syn/src/idl/file.rs:214:73
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

你可能还没有运行 anchor build

我使用的是 Mac,并且我收到错误:无法启动验证器:在 test-ledger 上创建分类账失败:块存储错误

请按照这个 Stack Exchange 线程 中的说明进行操作。

尽管我有 node.js,但 Mac 上没有 corepack

运行以下命令:

brew install corepack
brew link --overwrite corepack

来源 : https://stackoverflow.com/questions/70082424/command-not-found-corepack-when-installing-yarn-on-node-v17-0-1

error: 不是目录:

BPF SDK: /Users/rareskills/.local/share/solana/install/releases/stable-43daa37937907c10099e30af10a5a0b43e2dd2fe/solana-release/bin/sdk/bpf
cargo-build-bpf child: rustup toolchain list -v
cargo-build-bpf child: rustup toolchain link bpf /Users/rareskills/.local/share/solana/install/releases/stable-43daa37937907c10099e30af10a5a0b43e2dd2fe/solana-release/bin/sdk/bpf/dependencies/bpf-tools/rust
error: not a directory:

清除缓存:运行 rm -rf ~/.cache/solana/*

错误:target/idl/day_1.json 不存在。你是否运行了 anchor build

创建一个新项目并命名为 day_1,而不是 day1。Anchor 在某些机器上似乎默默插入下划线。

原文:https://learnblockchain.cn/article/11404

Solana基础 - 如何从钱包中获取所有 NFT

import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
import { publicKey } from "@metaplex-foundation/umi";
import { fetchAllDigitalAssetWithTokenByOwner } from "@metaplex-foundation/mpl-token-metadata";
import { clusterApiUrl } from "@solana/web3.js";

BigInt.prototype.toJSON = function () {
  return this.toString();
};

(async () => {
  try {
    // Create a UMI instance
    const umi = createUmi(clusterApiUrl("devnet"));

    // The owner's public key
    const ownerPublicKey = publicKey(
      "2R4bHmSBHkHAskerTHE6GE1Fxbn31kaD5gHqpsPySVd7",
    );

    console.log("Fetching NFTs...");
    const allNFTs = await fetchAllDigitalAssetWithTokenByOwner(
      umi,
      ownerPublicKey,
    );

    console.log(`Found ${allNFTs.length} NFTs for the owner:`);
    allNFTs.forEach((nft, index) => {
      console.log(`\nNFT #${index + 1}:`);
      console.log("Mint Address:", nft.publicKey);
      console.log("Name:", nft.metadata.name);
      console.log("Symbol:", nft.metadata.symbol);
      console.log("URI:", nft.metadata.uri);
    });

    // If you need the full NFT data
    console.log("\nFull NFT data:");
    console.log(JSON.stringify(allNFTs, null, 2));
  } catch (error) {
    console.error("Error:", error);
  }
})();

https://solana.com/zh/developers/cookbook/tokens/fetch-all-nfts

Solana基础 - 如何获得 NFT 的所有者

如果你拥有 NFT 的铸币密钥,你可以通过偷看该铸币密钥的最大代币账户来找到其当前所有者。

请记住,NFT 的供应量为 1,并且它们是不可分割的,这意味着在任何时间点只有一个代币账户会持有该代币,而该铸币密钥的所有其他代币账户的余额为 0。

一旦确定了最大的代币账户,我们就可以检索其所有者。

import { Connection, PublicKey } from "@solana/web3.js";

(async () => {
  const connection = new Connection("https://api.mainnet-beta.solana.com");
  const tokenMint = "9ARngHhVaCtH5JFieRdSS5Y8cdZk2TMF4tfGSWFB9iSK";

  const largestAccounts = await connection.getTokenLargestAccounts(
    new PublicKey(tokenMint),
  );
  const largestAccountInfo = await connection.getParsedAccountInfo(
    largestAccounts.value[0].address,
  );
  console.log(largestAccountInfo?.value?.data);

  const owner = largestAccountInfo?.value?.data?.parsed.info.owner;
  console.log("NFT owner :", owner);
})();

https://solana.com/zh/developers/cookbook/tokens/get-nft-owner

Solana基础 - 如何获取 NFT 元数据

import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
import {
  createSignerFromKeypair,
  generateSigner,
  signerIdentity,
} from "@metaplex-foundation/umi";
import {
  fetchDigitalAsset,
  mplTokenMetadata,
} from "@metaplex-foundation/mpl-token-metadata";
import { PublicKey } from "@metaplex-foundation/js";

(async () => {
  try {
    // Create a UMI instance
    const umi = createUmi("https://api.mainnet-beta.solana.com");

    // Use the mplTokenMetadata plugin
    umi.use(mplTokenMetadata());

    // Generate a new keypair (you can replace this with your own keypair if needed)
    const keypair = generateSigner(umi);
    umi.use(signerIdentity(createSignerFromKeypair(umi, keypair)));

    // The mint address of the NFT you want to fetch
    const mintAddress = new PublicKey(
      "Ay1U9DWphDgc7hq58Yj1yHabt91zTzvV2YJbAWkPNbaK",
    );

    console.log("Fetching NFT metadata...");
    const asset = await fetchDigitalAsset(umi, mintAddress);

    console.log("NFT Metadata:");

    // If you want to access specific metadata fields:
    console.log("\nName:", asset.metadata.name);
    console.log("Symbol:", asset.metadata.symbol);
    console.log("URI:", asset.metadata.uri);

    // Fetch and log the JSON metadata
    if (asset.metadata.uri) {
      const response = await fetch(asset.metadata.uri);
      const jsonMetadata = await response.json();
      console.log("\nJSON Metadata:");
      console.log(JSON.stringify(jsonMetadata, null, 2));
    }
  } catch (error) {
    console.error("Error:", error);
  }
})();

https://solana.com/zh/developers/cookbook/tokens/fetch-nft-metadata

Solana基础 - 如何创建 NFT

要创建 NFT,您必须

  • 将图像上传到 IPFS(例如 Arweave)
  • 将 JSON 元数据上传到 Arweave 或类似的存储服务。
  • 调用 metaplex 为 NFT 创建账户

上传至 Arweave

import fs from "node:fs";
import Arweave from "arweave";

(async () => {
  const arweave = Arweave.init({
    host: "localhost",
    port: 1984,
    protocol: "http",
    timeout: 20000,
    logging: false,
  });

  const host = arweave.getConfig().api.host;
  const port = arweave.getConfig().api.port;
  const protocol = arweave.getConfig().api.protocol;

  // Upload image to Arweave
  const data = fs.readFileSync("./code/nfts/upload-arweave/lowres-dog.png");

  const transaction = await arweave.createTransaction({
    data: data,
  });

  transaction.addTag("Content-Type", "image/png");

  // Instead of generating a new wallet, you can use an existing one from your file system
  // useful in production environments
  // const wallet = JSON.parse(fs.readFileSync("./code/nfts/upload-arweave/wallet.json", "utf-8"))
  const wallet = await arweave.wallets.generate();
  const address = await arweave.wallets.getAddress(wallet);
  console.log("address:", address);

  await arweave.api.get(`/mint/${encodeURI(addr)}/10000000000000000`);
  await arweave.transactions.sign(transaction, wallet);

  const response = await arweave.transactions.post(transaction);
  console.log(response);

  const id = transaction.id;
  const imageUrl = id ? `${protocol}://${host}:${port}/${id}` : null;
  console.log("imageUrl", imageUrl);

  // Upload metadata to Arweave

  const metadata = {
    name: "Custom NFT #1",
    symbol: "CNFT",
    description: "A description about my custom NFT #1",
    seller_fee_basis_points: 500,
    external_url: "https://www.customnft.com/",
    attributes: [
      {
        trait_type: "NFT type",
        value: "Custom",
      },
    ],
    collection: {
      name: "Test Collection",
      family: "Custom NFTs",
    },
    properties: {
      files: [
        {
          uri: imageUrl,
          type: "image/png",
        },
      ],
      category: "image",
      maxSupply: 0,
      creators: [
        {
          address: "CBBUMHRmbVUck99mTCip5sHP16kzGj3QTYB8K3XxwmQx",
          share: 100,
        },
      ],
    },
    image: imageUrl,
  };

  const metadataString = JSON.stringify(metadata);

  const metadataTransaction = await arweave.createTransaction({
    data: metadataString,
  });

  metadataTransaction.addTag("Content-Type", "application/json");

  await arweave.transactions.sign(metadataTransaction, wallet);

  console.log("metadata txid", metadataTransaction.id);

  const txnResult = await arweave.transactions.post(metadataTransaction);

  console.log(txnResult);
})();

Mint the NFT

import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
import {
  generateSigner,
  percentAmount,
  keypairIdentity,
} from "@metaplex-foundation/umi";
import { clusterApiUrl } from "@solana/web3.js";
import {
  createNft,
  fetchDigitalAsset,
  mplTokenMetadata,
} from "@metaplex-foundation/mpl-token-metadata";
import "dotenv/config";

(async () => {
  try {
    console.log("Loading keypair from environment...");
    const privateKey = JSON.parse(process.env.SOLANA_PRIVATE_KEY || "[]");
    if (privateKey.length === 0) {
      throw new Error("SOLANA_PRIVATE_KEY is not set in .env file");
    }

    console.log("Creating Umi instance...");
    const umi = createUmi(clusterApiUrl("devnet"));

    const keypair = umi.eddsa.createKeypairFromSecretKey(
      new Uint8Array(privateKey),
    );

    // Use keypairIdentity to set the keypair as the signer
    const signer = keypairIdentity(keypair);
    umi.use(signer);
    umi.use(mplTokenMetadata());

    console.log("Keypair loaded. Public key:", keypair.publicKey);

    console.log("Generating new mint address...");
    const mint = generateSigner(umi);

    console.log("Creating NFT...");
    const { signature } = await createNft(umi, {
      mint,
      name: "My NFT",
      // Replace this with your Arweave metadata URI
      uri: "https://ffaaqinzhkt4ukhbohixfliubnvpjgyedi3f2iccrq4efh3s.arweave.net/KUAIIbk6p8oo4XHRcq0U__C2r0mwQaNl0gQow4Qp9yk",
      maxSupply: 1,
      sellerFeeBasisPoints: percentAmount(0),
      creators: [
        {
          address: keypair.publicKey,
          share: 100,
          verified: true,
        },
      ],
    }).sendAndConfirm(umi);

    console.log("NFT created successfully!");
    console.log("Mint address:", mint.publicKey);
    console.log("Transaction signature:", signature);

    console.log("Fetching digital asset...");
    const asset = await fetchDigitalAsset(umi, mint.publicKey);
    console.log("Digital Asset:", asset);
  } catch (error) {
    console.error("Error:", error);
    console.error("Stack trace:", error.stack);
  }
})();

https://solana.com/zh/developers/cookbook/tokens/create-nft