NipGeihou's blog NipGeihou's blog
  • Java

    • 开发规范
    • 进阶笔记
    • 微服务
    • 快速开始
    • 设计模式
  • 其他

    • Golang
    • Python
    • Drat
  • Redis
  • MongoDB
  • 数据结构与算法
  • 计算机网络
  • 应用

    • Grafana
    • Prometheus
  • 容器与编排

    • KubeSphere
    • Kubernetes
    • Docker Compose
    • Docker
  • 组网

    • TailScale
    • WireGuard
  • 密码生成器
  • 英文单词生成器
🍳烹饪
🧑‍💻关于
  • 分类
  • 标签
  • 归档

NipGeihou

我见青山多妩媚,料青山见我应如是
  • Java

    • 开发规范
    • 进阶笔记
    • 微服务
    • 快速开始
    • 设计模式
  • 其他

    • Golang
    • Python
    • Drat
  • Redis
  • MongoDB
  • 数据结构与算法
  • 计算机网络
  • 应用

    • Grafana
    • Prometheus
  • 容器与编排

    • KubeSphere
    • Kubernetes
    • Docker Compose
    • Docker
  • 组网

    • TailScale
    • WireGuard
  • 密码生成器
  • 英文单词生成器
🍳烹饪
🧑‍💻关于
  • 分类
  • 标签
  • 归档
  • 参考资料
  • 比特币

  • Solana

    • Solana最佳实践
      • 区块链概念
        • Coin(原生货币)与Token(代币)
        • smart contract - 智能合约
        • dApp
        • Web3
        • NFT
      • Solana概念
        • 特点
        • Solana Accounts - Solana 账户
        • Sending Transactions - 发送交易
        • Program Derived Addresses (PDAs) - 程序派生地址
        • Cross-Program Invocations (CPIs) - 跨程序调用
      • Solana Playground (Solpg) - 开发环境
        • 连接Playground
        • 创建或导入钱包密钥
        • 获取 Devnet SOL
        • 主要用途
        • 获取方法
      • 网络接口
        • 获取账户 - 钱包信息
        • 获取账户 - Token Extensions Program(代币扩展程序)
        • 获取账户 - Mint 账户
        • 转账SOL
        • 创建Token
      • 参考
  • 区块链
  • Solana
NipGeihou
2025-04-16
目录

Solana最佳实践

# 区块链概念

# Coin (原生货币) 与 Token (代币)

  • 所有区块链协议都有代表价值且可交易的 Coin。在 Solana 上,这种 Coin 被称为 SOL。
  • Token 是由各个项目在基础区块链 (base blockchain) 上构建的协议。所有区块链项目都是 “代币化的”。

# smart contract - 智能合约

智能合约是写入区块链代码并在满足条件时自动执行的合约。Solana 和某些其他协议可以做到这一点,但其他一些区块链(尤其是比特币)却无法做到。

# dApp

dApp 是一种去中心化的应用程序 (decentralized application) ,或者说是使用智能合约的应用。任何加密程序 —— 例如市场、区块链游戏或用于 DAO 治理的工具 —— 都属于 dApp。

# Web3

  • Web 1.0 是静态网站和超链接 (如 AOL 或 GeoCities)
  • Web 2 是社交媒体(如 Twitter、Facebook 和 等等)
  • Web 3 是围绕去中心化的思想构建的

# NFT

NFT 的全称是 “非同质化代币”。

# Solana 概念

# 特点

  • 非常快的确认时间:一个交易只需 400 毫秒就可以被整个网络验证,远快于其他区块链。
  • 低交易费用

# Solana Accounts - Solana 账户

# Sending Transactions - 发送交易

# Program Derived Addresses (PDAs) - 程序派生地址

# Cross-Program Invocations (CPIs) - 跨程序调用

# Solana Playground (Solpg) - 开发环境

无需安装任何东西,只需在浏览器访问 https://beta.solpg.io/ (opens new window),即可快速开发、部署和测试 Solana 程序。

# 连接 Playground

点击屏幕左下方的 Not connected 按钮。

# 创建或导入钱包密钥

  • 首次使用,点击 Save keypair 保存新创建的钱包
  • 下次时候时,可点击 import keypair 导入此前创建的钱包

操作后,窗口底部可看到钱包地址、SOL 余额和连接的集群(默认为 devnet)。

  • 钱包地址 (wallet address) :通常为 Ed25519 密钥对的 32 字节公钥 显示为 base-58 编码的字符串(例如,  7MNj7pL1y7XpPnN7ZeuaE4ctwg3WeufbX5o85sA91J1  )。相应的私钥会从此地址签署交易。在 Solana 上,地址是用户钱包、程序(智能合约)或网络上任何其他账户的唯一标识符。
  • 公共集群 (Common clusters):
    • devnet  :用于开发人员实验的开发网络
    • testnet  :为验证器测试保留的网络(请勿用作应用程序开发人员)
    • mainnet-beta  :用于实时交易的主要 Solana 网络

# 获取 Devnet SOL

在开始开发之前,需要获取一些 devnet SOL 。

# 主要用途

  • 创建新帐户以在网络上存储数据或部署程序
  • 与 Solana 网络交互时支付交易费

# 获取方法

方法一:使用 Playground 终端

# 给自己空投5个sol
solana airdrop 5

方法二:使用 Devnet Faucet

  • 访问 https://faucet.solana.com/ (opens new window)
  • 输入 Playground 屏幕下方显示的钱包地址,填写金额
  • 点击 “Confirm Airdrop” 即可收到 devnet SOL

# 网络接口

在 Solana 上,所有数据都存在于 “account” 中。可理解为所有数据都存储在一个名为 account 的 Map, key 为钱包地址, value 为账号类型。

pub struct Account {
    /// lamports in the account
    pub lamports: u64,
    /// data held in this account
    #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
    pub data: Vec<u8>,
    /// the program that owns this account. If executable, the program that loads this account.
    pub owner: Pubkey,
    /// this account's data contains a loaded program (and is now read-only)
    pub executable: bool,
    /// the epoch at which this account will next owe rent
    pub rent_epoch: Epoch,
}

image.png

Solana 帐户包含以下任一内容:

  • 状态:用于读写数据。这包括有关令牌、用户数据或程序中定义的其他数据的信息。
  • 可执行程序:包含 Solana 程序实际代码的账户。这些账户存储网络执行的指令。

# 获取账户 - 钱包信息

Note

钱包为 系统程序 (opens new window) ,Solana 的原生程序之一

点击此链接 (opens new window)在 Solana Playground 中打开示例

// 获取你的 Playground 钱包地址
const address = pg.wallet.publicKey;
// 获取该地址的账户的 `AccountInfo`
const accountInfo = await pg.connection.getAccountInfo(address);

console.log(JSON.stringify(accountInfo, null, 2));
  • 在 Playground 终端中,输入  run  命令并按回车键,运行代码,得到以下输出
Running client...
  client.ts:
    {
  "data": {
    "type": "Buffer",
    "data": []
  },
  "executable": false,
  "lamports": 16000000000,
  "owner": "11111111111111111111111111111111",
  "rentEpoch": 18446744073709552000,
  "space": 0
}
  • data  - 包含账户 “数据” 的字段。对于钱包来说,此字段为空(0 字节),但其他账户使用此字段以序列化字节缓冲区的形式存储任意数据。
  • executable  - 指示帐户是否可作为可执行程序运行的标志。对于钱包和存储状态的帐户,此标志显示  false  。
  • owner  - 显示哪个程序控制该帐户的字段。对于钱包, 系统程序所有者地址为  11111111111111111111111111111111  .
  • lamports  - 账户中的 lampors 余额(1 SOL = 1,000,000,000 lampors)。
  • rentEpoch  与 Solana 已弃用的租金收取机制相关的遗留字段(目前未使用)。
  • space  -  data  字段(不是  Account  类型中的字段)的字节容量(长度)

# 获取账户 - Token Extensions Program (代币扩展程序)

点击此链接 (opens new window)在 Solana Playground 中打开示例。您将看到以下代码:

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

const address = new PublicKey("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb");
const accountInfo = await pg.connection.getAccountInfo(address);

console.log(JSON.stringify(accountInfo, null, 2));

run 输出:

Running client...
  client.ts:
    {
  "data": {
    "type": "Buffer",
    "data": [
      2,
      0,
      //... additional bytes
      86,
      51
    ]
  },
  "executable": true,
  "lamports": 1141440,
  "owner": "BPFLoaderUpgradeab1e11111111111111111111111",
  "rentEpoch": 18446744073709552000,
  "space": 36
}

代币扩展程序 作为可执行程序账户运行,保持相同的  Account  结构。

  • executable  - 设置为  true  ,表示可执行程序帐户。
  • data  - 包含序列化数据(不同于钱包账户中的空数据)。程序账户的数据保存的是另一个账户(程序可执行数据账户)的地址,该账户包含程序的字节码。
  • owner  - 可升级伯克利数据包过滤器 (BPF) 加载程序 (  BPFLoaderUpgradeab1e11111111111111111111111  ) 拥有此帐户,其功能相当于管理可执行帐户的本机程序。

可以检查 Solana Explorer 中的 代币扩展程序帐户 (opens new window) 及其相应的 程序可执行数据帐户 (opens new window) 。

# 获取账户 - Mint 账户

点击此链接 (opens new window)在 Solana Playground 中打开示例。您将看到以下代码:

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

const address = new PublicKey("C33qt1dZGZSsqTrHdtLKXPZNoxs6U1ZBfyDkzmj6mXeR");
const accountInfo = await pg.connection.getAccountInfo(address);

console.log(JSON.stringify(accountInfo, null, 2));

在此示例中,代码获取 devnet 上现有 Mint 帐户的地址。

run 输出:

Running client... 
client.ts: 
{ 
"data": { 
"type": "Buffer", 
"data": [ 
1, 
0, 
//... additional bytes 
0, 
0 
] 
}, 
"executable": false, 
"lamports": 4176000, 
"owner": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", 
"rentEpoch": 18446744073709552000, 
"space": 430 
}
  • owner  - 代币扩展程序 (  TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb  ) 拥有 mint 账户。
  • executable  - 设置为  false  ,因为此帐户存储状态而不是可执行代码。
  • data  :包含有关代币的序列化数据(铸造权限、供应、小数等)。

反序列化 data 字段数据

import { PublicKey } from "@solana/web3.js";
import { getMint, TOKEN_2022_PROGRAM_ID } from "@solana/spl-token";

const address = new PublicKey("C33qt1dZGZSsqTrHdtLKXPZNoxs6U1ZBfyDkzmj6mXeR");
const mintData = await getMint(
  pg.connection,
  address,
  "confirmed",
  TOKEN_2022_PROGRAM_ID,
);

console.log(
  JSON.stringify(
    mintData,
    (key, value) => {
      // Convert BigInt to String
      if (typeof value === "bigint") {
        return value.toString();
      }
      // Handle Buffer objects
      if (Buffer.isBuffer(value)) {
        return `<Buffer ${value.toString("hex")}>`;
      }
      return value;
    },
    2,
  ),
);

- 使用 @solana/spl-token 库的 getMint 方法将 Mint 帐户的 data 反序列回 Mint 类型。

pub struct Mint {
    /// Optional authority used to mint new tokens. The mint authority may only
    /// be provided during mint creation. If no mint authority is present
    /// then the mint has a fixed supply and no further tokens may be
    /// minted.
    pub mint_authority: COption<Pubkey>,
    /// Total supply of tokens.
    pub supply: u64,
    /// Number of base 10 digits to the right of the decimal place.
    pub decimals: u8,
    /// Is `true` if this structure has been initialized
    pub is_initialized: bool,
    /// Optional authority to freeze token accounts.
    pub freeze_authority: COption<Pubkey>,
}
  • address  - Mint 账户的地址
  • mintAuthority  - 允许铸造新代币的权限
  • supply  —— 代币总供应量
  • decimals  - 代币的小数位数
  • isInitialized  - 程序是否初始化了 Mint 数据
  • freezeAuthority  - 允许冻结代币账户的权限
  • tlvData  - 令牌扩展的额外数据(需要进一步反序列化)

# 转账 SOL

import {
  LAMPORTS_PER_SOL,
  SystemProgram,
  Transaction,
  sendAndConfirmTransaction,
  Keypair,
} from "@solana/web3.js";

// 发送方钱包
const sender = pg.wallet.keypair;

// 新建一个钱包,作为接收方
const receiver = new Keypair();

// 构造一个转账指令来转移 0.01 SOL;即RequestBody
const transferInstruction = SystemProgram.transfer({
  fromPubkey: sender.publicKey,
  toPubkey: receiver.publicKey,
  lamports: 0.01 * LAMPORTS_PER_SOL,
});

// 构建包含转账指令的交易
const transaction = new Transaction().add(transferInstruction);

// 发送并确认交易
const transactionSignature = await sendAndConfirmTransaction(
  pg.connection,
  transaction,
  [sender],
);

// 在 Playground 终端中打印出 SolanaFM 浏览器的链接,以查看交易详情
console.log(
  "Transaction Signature:",
  `https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`,
);

输出:

Transaction Signature: https://solana.fm/tx/he9dBwrEPhrfrx2BaX4cUmUbY22DEyqZ837zrGrFRnYEBmKhCb5SvoaUeRKSeLFXiGxC8hFY5eDbHqSJ7NYYo42?cluster=devnet-solana

# 创建 Token

import {
  Connection,
  Keypair,
  SystemProgram,
  Transaction,
  clusterApiUrl,
  sendAndConfirmTransaction,
} from "@solana/web3.js";
import {
  MINT_SIZE,
  TOKEN_2022_PROGRAM_ID,
  createInitializeMint2Instruction,
  getMinimumBalanceForRentExemptMint,
} from "@solana/spl-token";

// 获取你的 Playground 钱包并创建与 Solana 开发网络的连接
const wallet = pg.wallet;
const connection = new Connection(clusterApiUrl("devnet"), "confirmed");

// 生成一个新的密钥对作为 Mint 账户的地址
const mint = new Keypair();

// 计算 Mint 账户所需的 Lamport
const rentLamports = await getMinimumBalanceForRentExemptMint(connection);

// 创建新帐户的指令与新的Mint帐户的空间
const createAccountInstruction = SystemProgram.createAccount({
  fromPubkey: wallet.publicKey, // 从钱包中转移 Lamport 资金到新账户
  newAccountPubkey: mint.publicKey, 
  space: MINT_SIZE, // 分配存储铸币数据所需的空间
  lamports: rentLamports,
  programId: TOKEN_2022_PROGRAM_ID, // 将帐户所有权分配给代币扩展程序 `TOKEN_2022_PROGRAM_ID`
});

// 构建代币扩展程序指令来初始化 Mint 账户数据
const initializeMintInstruction = createInitializeMint2Instruction(
  mint.publicKey,
  2, // decimals
  wallet.publicKey, // mint authority
  wallet.publicKey, // freeze authority
  TOKEN_2022_PROGRAM_ID,
);

// 将两条指令添加到单个交易中
const transaction = new Transaction().add(
  createAccountInstruction,
  initializeMintInstruction,
);

// 发送并确认交易,需要传入2个钱包密钥对
const transactionSignature = await sendAndConfirmTransaction(
  connection,
  transaction,
  [
    wallet.keypair, // 用于支付账户创建和交易费用的钱包密钥对
    mint, // 使用公钥作为账户地址来创建新账户的 mint 密钥对
  ],
);

console.log(
  "\nTransaction Signature:",
  `https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`,
);

console.log(
  "\nMint Account:",
  `https://solana.fm/address/${mint.publicKey}?cluster=devnet-solana`,
);

输出

Transaction Signature: https://solana.fm/tx/3BEjFxqyGwHXWSrEBnc7vTSaXUGDJFY1Zr6L9iwLrjH8KBZdJSucoMrFUEJgWrWVRYzrFvbjX8TmxKUV88oKr86g?cluster=devnet-solana

Mint Account: https://solana.fm/address/CoZ3Nz488rmATDhy1hPk5fvwSZaipCngvf8rYBYVc4jN?cluster=devnet-solana

# 参考

  • Learn how the Solana blockchain works | Solana (opens new window)
上次更新: 2025/05/10, 02:23:42
前置知识

← 前置知识

最近更新
01
磁盘管理与文件系统
05-02
02
网络测试 - iperf3
05-02
03
Docker Swarm
04-18
更多文章>
Theme by Vdoing | Copyright © 2018-2025 NipGeihou | 友情链接
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式