Skip to content

Solana 私有链与Token

bash
# macOS
brew install solana
bash
solana-keygen new
# 生成私钥文件 ~/.config/solana/id.json
# 如果要指定私钥文件生成的位置,使用 solana-keygen new -o key.json

查看账户地址:

bash
solana address

输出:

bash
2LJPdSc2GAsQrkbn8mN1DMPwAi5FEFqnesNLBH9rK41L

Docker 启动私有链docker-compose.yaml

yaml
version: '3.8'

services:
  solana-test-validator:
    image: tchambard/solana-test-validator:latest
    container_name: solana-test-validator
    command: solana-test-validator --ledger ledger
    volumes:
      - /root/docker/solana-test-validator/ledger:/working-dir/ledger:rw
    ports:
      - "8899:8899"
      - "8900:8900"
    restart: unless-stopped

客户端配置:

bash
# Main Net
solana config set --url https://api.mainnet-beta.solana.com
# Dev Net
solana config set --url https://api.devnet.solana.com
# Local Net
solana config set --url http://127.0.0.1:8899
# Private Net
solana config set --url https://dev.flxdu.cn/solana

对账户~/.config/solana/id.json发放空投:

bash
solana airdrop 20

查询sol余额:

bash
solana balance

测试是否可用:

bash
solana ping
# 此操作会向链上发送交易,会产生Sol开销(但是损失很小)。

创建 Token

装个rust:

bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

安装 spl-token CLI

solana 包含了 spl-token 工具,因此只要你安装了 Solana CLI,你就可以直接使用 spl-token 工具。如果你没有安装 Solana CLI,可以通过以下步骤单独安装 spl-token

bash
cargo install spl-token-cli

cargo install 会自动从 Rust 的包管理系统(crates.io)下载并构建 spl-token-cli 工具。

验证安装

安装完成后,可以使用以下命令验证是否安装成功:

bash
spl-token --version

如果安装成功,会显示当前安装的 spl-token-cli 版本。

创建Token

接下来执行命令,都是以~/.config/solana/id.json私钥的身份执行的,如果有花费Sol的操作,均由~/.config/solana/id.json发出交易并支付Gas。

bash
spl-token create-token --enable-freeze

输出类似如下:

bash
Creating token 7ojF7rnLawUCKMXVTMmU6aPLfDKKvw9Lzfj24oLB1vXq under program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA

Address:  7ojF7rnLawUCKMXVTMmU6aPLfDKKvw9Lzfj24oLB1vXq
Decimals:  9

Signature: 2Pazt9uhHrbVv9bBdBuw5p6ypiVNtMPJZWh1CZZq6C1axRJszPSP7cZg8nJhdpRLRWSczfbxWM1KERwuYAwqyTPa

收到了代币 ID 和签名。然后我们可以利用代币 ID 来检查代币的发行量:

bash
spl-token supply 7ojF7rnLawUCKMXVTMmU6aPLfDKKvw9Lzfj24oLB1vXq

创建一个代币账户

bash
# ~/.config/solana/id.json 针对代币 7ojF7rnLawUCKMXVTMmU6aPLfDKKvw9Lzfj24oLB1vXq 的账户地址
spl-token create-account 7ojF7rnLawUCKMXVTMmU6aPLfDKKvw9Lzfj24oLB1vXq

输出:

bash
Creating account EMhH7nif2cGtCbRFzfW5zL7M5gskLZEPEAp98R6QSQCC

Signature: 5YSvadDyYFpLetkkGh8aWibWe46Kf4WeFCZ95wsj12YHPRcjavHAssNBQKsdZ3p6vMV5RYmbZYD8fnJ1YECGmVtR

发行代币

bash
spl-token mint 7ojF7rnLawUCKMXVTMmU6aPLfDKKvw9Lzfj24oLB1vXq 1000000000 EMhH7nif2cGtCbRFzfW5zL7M5gskLZEPEAp98R6QSQCC

输出如下:

bash
Minting 10000000 tokens
  Token: 7ojF7rnLawUCKMXVTMmU6aPLfDKKvw9Lzfj24oLB1vXq
  Recipient: EMhH7nif2cGtCbRFzfW5zL7M5gskLZEPEAp98R6QSQCC

Signature: 3pedDzGyHH5FshZKxnyjDTfhKE5cU6CTJqypiQgJPgcBsGhMcxrF4dRCCge7aSze3CCNBW58Z8PN1Gv1hZwbn7w7

检查账户余额: 一旦 mint 完成,你可以检查新代币账户的余额:

bash
# 此命令查询 ~/.config/solana/id.json 拥有代币 7ojF7rnLawUCKMXVTMmU6aPLfDKKvw9Lzfj24oLB1vXq 的数量
spl-token balance 7ojF7rnLawUCKMXVTMmU6aPLfDKKvw9Lzfj24oLB1vXq

创建一个账户2,用于接收转账:

bash
solana-keygen new -o solana2.key

账户2要创建针对此地址的代币账户:

bash
spl-token create-account 7ojF7rnLawUCKMXVTMmU6aPLfDKKvw9Lzfj24oLB1vXq

转账:

bash
# 从 ~/.config/solana/id.json 向 AX7iQ6senZ5NxDf6P63NGX2G7KTdywNuCtsSjDG9fuww 转出代币 7ojF7rnLawUCKMXVTMmU6aPLfDKKvw9Lzfj24oLB1vXq,数量100
spl-token transfer --fund-recipient 7ojF7rnLawUCKMXVTMmU6aPLfDKKvw9Lzfj24oLB1vXq 100 AX7iQ6senZ5NxDf6P63NGX2G7KTdywNuCtsSjDG9fuww

输出:

bash
Transfer 100 tokens
  Sender: EMhH7nif2cGtCbRFzfW5zL7M5gskLZEPEAp98R6QSQCC
  Recipient: AX7iQ6senZ5NxDf6P63NGX2G7KTdywNuCtsSjDG9fuww
  Recipient associated token account: AzHb1tc4dbKjYhYr2krkKjjedwXGFuWzYz4vzFPwQ96i
  Funding recipient: AzHb1tc4dbKjYhYr2krkKjjedwXGFuWzYz4vzFPwQ96i

Signature: bKVqSEtB6miMAgDuJikJcr4STgmuPHksM1Zj8sbeWVAvBhJqPSNkYd3NxUJa2aX9miuKv2jtcZDtdz4uwNNmGvX

查询默认账户余额:

bash
spl-token balance 7ojF7rnLawUCKMXVTMmU6aPLfDKKvw9Lzfj24oLB1vXq
# 输出 9999900

查询账户2余额:

bash
spl-token balance 7ojF7rnLawUCKMXVTMmU6aPLfDKKvw9Lzfj24oLB1vXq --owner AX7iQ6senZ5NxDf6P63NGX2G7KTdywNuCtsSjDG9fuww
# 输出 100

冻结账户:

bash
# 需要使用账户地址
spl-token freeze AzHb1tc4dbKjYhYr2krkKjjedwXGFuWzYz4vzFPwQ96i

获取一个地址<OWNER_ADDRESS>针对代币 <TOKEN_MINT_ADDRESS> 的账户地址:

bash
spl-token address --verbose --token <TOKEN_MINT_ADDRESS> --owner <OWNER_ADDRESS>

放弃一些权力:

bash
spl-token authorize <mint_address> mint --disable # 放弃铸币权
spl-token authorize <mint_address> freeze --disable # 放弃冻结权

Pinata 将Logo图片上传至IPFS。

将Logo链接写到此文件中:

bash
{
    "name": "White Dog Token",
    "symbol": "WDT",
    "description": "A white dog token",
    "image": "https://scarlet-faithful-marten-470.mypinata.cloud/ipfs/bafkreig7mn5t6galamlqesnmq54h32wikr4s2qmplew4kh6dufgycejati",
    "attributes": []
}

把此文件也上传至 IPFS,得到此文件的链接:https://scarlet-faithful-marten-470.mypinata.cloud/ipfs/bafkreiajb5eihbx5xhbqiblhqeo7345woynsqm4i7gj5yb7qytb2zcu52q

执行代码即可,注意修改其中的参数:

javascript
const {
    createMetadataAccountV3
} = require("@metaplex-foundation/mpl-token-metadata");
const web3 = require("@solana/web3.js");
const {createSignerFromKeypair, none, signerIdentity} = require("@metaplex-foundation/umi");
const {createUmi} = require('@metaplex-foundation/umi-bundle-defaults');
const {fromWeb3JsKeypair, fromWeb3JsPublicKey} = require('@metaplex-foundation/umi-web3js-adapters');

// 从文件加载钱包密钥
function loadWalletKey(keypairFile) {
    const fs = require("fs");
    return web3.Keypair.fromSecretKey(
        new Uint8Array(JSON.parse(fs.readFileSync(keypairFile).toString()))
    );
}

async function main() {
    console.log("RUN...");

    // 加载钱包密钥对并设置铸币地址
    const myKeypair = loadWalletKey("~/.config/solana/id_devnet.json");
    const mint = new web3.PublicKey("3k5asAfFbvz7jZ3sTFqGCxyRPvNTNSD1kLouXUWe9QP2");

    // 使用自定义 RPC 连接到 Solana 开发网络
    // const umi = createUmi("https://dev.flxdu.cn/solana");
    const umi = createUmi("https://devnet.helius-rpc.com/?api-key=YourAPIKey");
    // const umi = createUmi("https://api.devnet.solana.com");
    // const umi = createUmi("https://api.mainnet-beta.solana.com");

    // 设置签名者身份
    const signer = createSignerFromKeypair(umi, fromWeb3JsKeypair(myKeypair));
    umi.use(signerIdentity(signer, true));

    // 定义代币的元数据
    const onChainData = {
        name: "White Dog Token", // 代币名称
        symbol: "WDT", // 代币符号
        uri: "https://scarlet-faithful-marten-470.mypinata.cloud/ipfs/bafkreiajb5eihbx5xhbqiblhqeo7345woynsqm4i7gj5yb7qytb2zcu52q", // 元数据 JSON 文件链接
        sellerFeeBasisPoints: 0,  // 设置销售费用为0
        creators: none(),
        collection: none(),
        uses: none(),
    };

    // 设置铸币授权和铸币地址
    const accounts = {
        mint: fromWeb3JsPublicKey(mint),
        mintAuthority: signer,
    };

    // 创建元数据账户
    const txid = await createMetadataAccountV3(umi, {
        ...accounts,
        isMutable: true, // 设置元数据可修改
        collectionDetails: null,
        data: onChainData,
    }).sendAndConfirm(umi);

    console.log(txid);
}

main();

随后在浏览器上就可以看到:https://explorer.solana.com/address/3k5asAfFbvz7jZ3sTFqGCxyRPvNTNSD1kLouXUWe9QP2?cluster=devnet

添加流动性

通过Raydium来添加流动性,raydium在主网和开发网上都部署了相关程序,地址在这里:https://docs.raydium.io/raydium/protocol/developers/addresses。

Raydium 的网站虽然允许设置自定义RPC连接,但是他仅支持主网,也就是说自定义RPC连接也必须是指向主网的RPC链接。

image-20250122030559977

solana 私有链 Nginx 反向代理

Docker 启动私有链之后,可以使用 Nginx 做反向代理:

nginx
   location /solana {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_read_timeout 600s;
        proxy_send_timeout 600s;

        if ($http_upgrade = "websocket") {
            proxy_pass http://host.docker.internal:8900;
            break;
        }

        proxy_pass http://host.docker.internal:8899;
    }

Base58 私钥转 JSON

python
import base58
import json
from solders.keypair import Keypair


def base58_to_json(private_key_base58: str) -> list:
    # 将 Base58 编码的私钥转换为字节
    private_key_bytes = base58.b58decode(private_key_base58)

    # 确保私钥长度是 64 字节
    if len(private_key_bytes) != 64:
        raise ValueError("私钥长度不正确,应为 64 字节")

    # 创建一个 Solana Keypair 对象
    keypair = Keypair.from_bytes(private_key_bytes)
    pubkey = json.loads(keypair.pubkey().to_json())
    secretkey = list(keypair.secret())
    return secretkey + pubkey


# 将包含私钥和公钥的 JSON 数据转换回 Base58 编码的私钥字符串。
def json_to_base58(key: list) -> str:
    # 提取密钥信息
    if not isinstance(key, list) or len(key) != 64:
        raise ValueError("输入的 JSON 数据格式不正确,应为包含 64 个字节的列表")

    # 将密钥列表转换为字节数组
    private_key_bytes = bytes(key)

    # 确保长度为 64 字节
    if len(private_key_bytes) != 64:
        raise ValueError("密钥字节数组长度不正确,应为 64 字节")

    # 编码为 Base58
    private_key_base58 = base58.b58encode(private_key_bytes).decode("utf-8")
    return private_key_base58


if __name__ == "__main__":
    key_base58 = ""  # 私钥 Base58 字符串
    assert (len(key_base58) != 0)
    res = base58_to_json(key_base58)
    outputFile = "keypair_devnet.json"
    with open(outputFile, "w") as f:
        json.dump(res, f)

    with open(outputFile, "r") as f:
        assert json_to_base58(json.load(f)) == key_base58

修改私钥配置:

bash
solana config set --keypair keypair_devnet.json