目录
1.Solana 核心概念简述
1.1. 账户(Account)
1.2. 交易(Transaction)
1.3. 交易指令(Instruction)
1.4. SPL 代币
1.5. 合约(Program)
1.6. 租约(Rent)
2通过RPC与Solana交互
2.1.配置环境
2.2.查看钱包信息
2.3.Token 账户查询
2.4.创建 Token 账户
2.5.实时监控账户变动
2.6.转账操作
3.Solana的Web3.js
3.1.交易代码
3.2.输出结果
4.通过 WalletAdatper 与钱包交互
4.1 依赖和示例界面
4.2Solana 钱包 API 整理
4.2.1关键包和导入
4.2.2SolongWallet 适配器集成
4.2.3钱包连接和状态管理
4.2.4 交易操作
4.2.5 RPC 配置和管理
1.Solana 核心概念简述
Solana 是一个高性能的区块链平台,其核心概念包括 账户(Account)、交易(Transaction)、合约(Program) 和 租约(Rent)。以下是对这些概念的简短易懂概括:
1.1. 账户(Account)
- “一切皆账户”:类似 Linux 系统把所有资源抽象为“文件”,Solana 中所有的信息(如代币余额、合约代码等)都存储在账户对象中。
- 账户结构:
- lamports:账户余额(Solana 的原生代币单位)。
- data:存储的数据(对于合约账户,这里是代码;对于用户账户,这里可能是代币信息)。
- owner:账户的所有者(通常是一个合约地址,决定谁能操作这个账户)。
- executable:是否为可执行合约(如果是合约账户,此值为 true)。
- rent_epoch:下次需要支付租金的周期。
- 账户与签名:
1.2. 交易(Transaction)
- 用户账户本质是一个 私钥,通过 Ed25519 算法生成(高效、安全、签名短)。
- 账户地址是私钥对应的 公钥,经过 Base58 编码后变成可读形式(如 HawRVHh7t4d3H3bitWHFt25WhhoDmbJMCfWdESQQoYEy)。
- 私钥签名用于证明对账户的控制权。
- 定义:交易是用户与区块链交互的方式,例如转账或调用合约。
- 组成:
- 一系列 交易指令(Instruction):指定要执行的操作、涉及的账户和数据。
- 签名:由发起者的私钥生成,用于验证交易有效性。
- 账户列表:交易中涉及的所有账户地址。
- 最近区块哈希:确保交易的时效性。
1.3. 交易指令(Instruction)
- 定义:交易的核心单元,描述具体的操作。
- 结构:
- program_id_index:要调用的合约地址(在账户列表中的索引)。
- accounts:操作涉及的账户。
- data:输入的具体数据。
1.4. SPL 代币(SPL)
在以太坊中,普通代币被一个叫ERC20的提案定了规范,可以认为普通代币合约统一叫做ERC20代币。
那么Solana世界里的ERC20代币是什么呢?答案就是SPL代币。
The Solana Program Library (SPL) is a collection of on-chain programs targeting the Sealevel parallel runtime.
SPL Token是 " Solana Program Library"中的一个组成部分,叫做"Token Program",简称为SPL Token。
所有的代币都有这个合约来管理,该合约代码在 https://github.com/solana-labs/solana-program-library/tree/master/token
不同于以太坊中,一个代币就是一个合约。
SPL Token中,一个代币,仅仅是一个归Token合约管理的普通的Account对象,这个对象里面的二进制数据定义了 这个代币的基本属性
1.5. 合约(Program)
- 类型:
- 系统合约(Native Program):由 Solana 官方提供,如 System Program(创建账户、转账)、BPF Loader Program(部署合约)。
- 普通合约(On Chain Program):用户开发并部署的合约,如代币相关合约。
- 特点:
- 合约是存储在账户中的可执行代码(executable = true)。
- 用户通过交易调用合约执行逻辑。
- Solana 的合约可以更新或销毁,销毁时存储资源会退还给部署者。
1.6. 租约(Rent)
- 什么是租约:
- 账户需要支付“租金”来保持数据存储在区块链上,租金根据存储的数据量和时间计算。
- 规则:
- 免租:账户余额超过“两年租金”的阈值则无需支付租金。
- 租金不足:余额低于免租阈值或付不出租金的账户会被“垃圾收集”(删除)。
- 与交易费用的区别:
- 租金是为存储付费。
- 交易费用是为处理指令付费。
2通过RPC与Solana交互
2.1.配置环境
首先,将钱包配置指向 Devnet 环境:
solana config set --url https://api.devnet.solana.com
2.2.查看钱包信息
查看当前钱包地址:
solana address
获取测试网 SOL(开发资金):
solana airdrop 2
查看钱包 SOL 余额:
solana balance
查看钱包中所有 SPL Token 余额:
spl-token accounts
2.3.Token 账户查询
通过这个 curl
请求,你可以查询 Czorr4y9oFvE3VdfCLVFuKDYxaNUG1iyQomR7kMZUuzi
这个钱包地址下,所有与 7vtXvye2ECB1T5Se8E1KebNfmV7t4VkaULDjf2v1xpA9
这个 SPL Token(Token 的 Mint 地址)相关的 Token 账户。
curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getTokenAccountsByOwner",
"params": [
"Czorr4y9oFvE3VdfCLVFuKDYxaNUG1iyQomR7kMZUuzi",
{
"mint": "7vtXvye2ECB1T5Se8E1KebNfmV7t4VkaULDjf2v1xpA9"
},
{
"encoding": "jsonParsed"
}
]
}
'
查询结果解析:
{
"jsonrpc": "2.0",
"result": {
"context": {
"apiVersion": "2.1.13",
"slot": 363620820
},
"value": [
{
"account": {
"data": {
"parsed": {
"info": {
"isNative": false,
"mint": "7vtXvye2ECB1T5Se8E1KebNfmV7t4VkaULDjf2v1xpA9",
"owner": "Czorr4y9oFvE3VdfCLVFuKDYxaNUG1iyQomR7kMZUuzi",
"state": "initialized",
"tokenAmount": {
"amount": "94000000000",
"decimals": 9,
"uiAmount": 94,
"uiAmountString": "94"
}
},
"type": "account"
},
"program": "spl-token",
"space": 165
},
"executable": false,
"lamports": 315139280,
"owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
"rentEpoch": 18446744073709552000,
"space": 165
},
"pubkey": "EZhhUANUMKsRhRMArczio1kLc9axefTUAh5xofGX35AK"
}
]
},
"id": 1
}
pubkey
:EZhhUANUMKsRhRMArczio1kLc9axefTUAh5xofGX35AK
- Token 账户公钥mint
:7vtXvye2ECB1T5Se8E1KebNfmV7t4VkaULDjf2v1xpA9
- SPL Token 的类型标识符owner
:Czorr4y9oFvE3VdfCLVFuKDYxaNUG1iyQomR7kMZUuzi
- 该 Token 账户的拥有者钱包地址tokenAmount
:当前持有的代币数量(带有小数位信息)
2.4.创建 Token 账户
为某个 SPL Token 创建关联账户(需要确保接收者有对应的 Token 账户才能接收特定代币):
spl-token create-account 9pbWQJHTSLELGekc85QHYCHozRdLo4si2nCPbMNZ2bxj --owner HybiEijLUVae5NFoiW52jCoN2bQ9TUL74wJkfPRgHxbm --fee-payer /Users/linzhihao/.config/solana/id.json
2.5.实时监控账户变动
使用 WebSocket 订阅账户变化(需要提前安装 wscat 工具):
wscat -c wss://api.devnet.solana.com
连接后发送订阅请求:
{"jsonrpc":"2.0","id":1,"method":"accountSubscribe","params":["HybiEijLUVae5NFoiW52jCoN2bQ9TUL74wJkfPRgHxbm",{"encoding":"jsonParsed","commitment":"finalized"}]}
监听Solana token account HybiEijLUVae5NFoiW52jCoN2bQ9TUL74wJkfPRgHxbm
2.6.转账操作
转移 SPL Token 到另一个钱包(自动为对方创建接收账户):
spl-token transfer --fund-recipient 9pbWQJHTSLELGekc85QHYCHozRdLo4si2nCPbMNZ2bxj 0.01 BBy1K96Y3bohNeiZTHuQyB53LcfZv6NWCSWqQp89TiVu
--fund-recipient
:如果目标地址没有该Token的账户,则为其创建一个。1
:转账的Token数量,1表示转1个单位的该Token。BBy1K96Y3bohNeiZTHuQyB53LcfZv6NWCSWqQp89TiVu
:目标收件人的Solana Public Address
3.Solana的Web3.js
3.1.交易代码
// solana-test.js
const { Connection, Keypair, LAMPORTS_PER_SOL, PublicKey, SystemProgram, TransactionMessage, VersionedTransaction } = require('@solana/web3.js');
const fs = require('fs');
const bs58 = require('bs58');
async function main() {
try {
// 1. 读取 id.json 文件
const idJson = JSON.parse(fs.readFileSync('/Users/linzhihao/.config/solana/id.json', 'utf8'));
// 2. 解码为 Uint8Array
const secretKey = new Uint8Array(idJson);
// 3. 创建 Keypair
const keypair = Keypair.fromSecretKey(secretKey);
console.log('✅ Loaded keypair from secret key');
console.log(` Address: ${keypair.publicKey.toString()}`);
// 4. 连接到 Solana 网络
const connection = new Connection('https://api.devnet.solana.com', 'confirmed');
// 5. Check account balance
const balance = await connection.getBalance(keypair.publicKey);
console.log(`✅ Current balance: ${balance / LAMPORTS_PER_SOL} SOL`);
// If balance is too low, request an airdrop
if (balance < 0.1 * LAMPORTS_PER_SOL) {
console.log('⏳ Requesting airdrop of 1 SOL...');
const airdropSignature = await connection.requestAirdrop(
keypair.publicKey,
1 * LAMPORTS_PER_SOL
);
// Wait for confirmation
const latestBlockhash = await connection.getLatestBlockhash('finalized');
await connection.confirmTransaction({
blockhash: latestBlockhash.blockhash,
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
signature: airdropSignature
});
const newBalance = await connection.getBalance(keypair.publicKey);
console.log(`✅ New balance after airdrop: ${newBalance / LAMPORTS_PER_SOL} SOL`);
}
// 6. Set up recipient address for transfer (just an example address)
const recipientAddress = new PublicKey('Ey9M2QMRfg8Tuysyh3UEdeB9NwZmRaJU5FUjKtBSNJnb');
const transferAmount = 0.01 * LAMPORTS_PER_SOL; // 0.01 SOL
console.log(`⏳ Preparing to send ${transferAmount / LAMPORTS_PER_SOL} SOL to ${recipientAddress.toString()}...`);
// 7. Get latest blockhash
const latestBlockhash = await connection.getLatestBlockhash('finalized');
console.log(`✅ Fetched latest blockhash. Last Valid Height: ${latestBlockhash.lastValidBlockHeight}`);
// 8. Create a transfer instruction
const transferInstruction = SystemProgram.transfer({
fromPubkey: keypair.publicKey,
toPubkey: recipientAddress,
lamports: transferAmount
});
// 9. Create a transaction message
const message = new TransactionMessage({
payerKey: keypair.publicKey,
recentBlockhash: latestBlockhash.blockhash,
instructions: [transferInstruction]
}).compileToV0Message();
// 10. Create a versioned transaction
const transaction = new VersionedTransaction(message);
// 11. Sign the transaction
transaction.sign([keypair]);
console.log('✅ Transaction signed');
// 12. Send the transaction
const signature = await connection.sendTransaction(transaction);
console.log(`✅ Transaction sent with signature: ${signature}`);
// 13. Confirm the transaction
console.log('⏳ Confirming transaction...');
await connection.confirmTransaction({
blockhash: latestBlockhash.blockhash,
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
signature: signature
});
console.log('✅ Transaction confirmed');
// 14. Check the new balance
const newBalance = await connection.getBalance(keypair.publicKey);
console.log(`✅ New balance after transfer: ${newBalance / LAMPORTS_PER_SOL} SOL`);
} catch (error) {
console.error('❌ Error:', error);
}
}
main();
3.2.输出结果
✅ Loaded keypair from secret key
Address: Ed5NC8cwDiJ7A8831DoAdELq6aouydCbywUDLg3EwC8k
✅ Current balance: 2.89027628 SOL
⏳ Preparing to send 0.01 SOL to Ey9M2QMRfg8Tuysyh3UEdeB9NwZmRaJU5FUjKtBSNJnb...
✅ Fetched latest blockhash. Last Valid Height: 351840779
✅ Transaction signed
✅ Transaction sent with signature: 2x4DEvKHvuXDtrFUfPhLL1EcicYTf1AKKuuer6gU5o2Rrt14sa85uRGgrApZJUPLJod6JdgGuezwVBbb5x3RLs7n
⏳ Confirming transaction...
✅ Transaction confirmed
✅ New balance after transfer: 2.88027128 SOL
4.通过 WalletAdatper 与钱包交互
4.1 依赖和示例界面
npm install \
@solana/wallet-adapter-react@0.15.35 \
@solana/wallet-adapter-base@0.9.23 \
@solana/wallet-adapter-wallets@0.17.0 \
npm install @solana/buffer-layout
npm install @solana/web3.js@latest
npm install @craco/craco --save-dev
npm install web-vitals
4.2Solana 钱包 API 整理
4.2.1关键包和导入
// 核心适配器和连接
import { ConnectionProvider, WalletProvider, useConnection, useWallet } from '@solana/wallet-adapter-react';
import { WalletAdapterNetwork } from '@solana/wallet-adapter-base';
import { WalletModalProvider } from '@solana/wallet-adapter-react-ui';
// UI 组件
import { WalletDisconnectButton, WalletMultiButton } from '@solana/wallet-adapter-react-ui';
// 特定钱包适配器
import { SolongWalletAdapter } from '@solana/wallet-adapter-solong';
import { PhantomWalletAdapter } from '@solana/wallet-adapter-phantom';
// Solana Web3 工具
import { Keypair, Connection, LAMPORTS_PER_SOL, PublicKey, SystemProgram, Transaction } from '@solana/web3.js';
4.2.2SolongWallet 适配器集成
// 初始化 SolongWallet 适配器
const solongAdapter = new SolongWalletAdapter();
// 组合多个钱包适配器
const wallets = useMemo(
() => [
new PhantomWalletAdapter(),
new SolongWalletAdapter(),
// 可以添加更多适配器...
],
[]
);
// 提供钱包适配器给应用
<WalletProvider wallets={wallets} autoConnect>
{/* 应用组件 */}
</WalletProvider>
4.2.3钱包连接和状态管理
<ConnectionProvider endpoint={endpoint}>
<WalletProvider wallets={wallets} autoConnect>
<WalletModalProvider>
{/* 应用组件 */}
</WalletModalProvider>
</WalletProvider>
</ConnectionProvider>
<div style={styles.walletButtons}>
<WalletMultiButton />
<WalletDisconnectButton />
</div>
4.2.4 交易操作
// 在组件内部获取钱包连接和功能
const { connection } = useConnection();
const { publicKey, sendTransaction } = useWallet();
// 创建交易
const transaction = new Transaction();
const recipientPubKey = Keypair.generate().publicKey;
// 添加转账指令
transaction.add(
SystemProgram.transfer({
fromPubkey: publicKey,
toPubkey: recipientPubKey,
lamports: 0.01 * LAMPORTS_PER_SOL,
})
);
// 获取最新区块哈希
const { blockhash, lastValidBlockHeight } = value;
transaction.recentBlockhash = blockhash;
transaction.feePayer = publicKey;
// 发送交易
const signature = await sendTransaction(transaction, connection, {
minContextSlot,
skipPreflight: false
});
// 等待确认
const confirmationResponse = await connection.confirmTransaction({
blockhash,
lastValidBlockHeight,
signature,
}, 'confirmed');
4.2.5 RPC 配置和管理
export const RPC_CONFIG = {
// 默认 Solana Devnet - 有低速率限制
DEFAULT: clusterApiUrl(WalletAdapterNetwork.Devnet),
// 替代端点 - 在这里添加您的 API 密钥
HELIUS: 'https://rpc-devnet.helius.xyz/your-api-key',
QUICKNODE: 'https://your-quicknode-devnet-endpoint',
ALCHEMY: 'https://solana-devnet.g.alchemy.com/v2/your-api-key',
// 默认使用哪个端点
ACTIVE: 'DEFAULT',
// 速率限制错误的最大重试次数
MAX_RETRIES: 3,
// 指数退避的基本延迟(毫秒)
BASE_DELAY: 1000,
// 余额轮询间隔(毫秒)- 增加到 30 秒
POLLING_INTERVAL: 30000,
};