跳转到主要内容

Documentation Index

Fetch the complete documentation index at: https://docs.raydium.io/llms.txt

Use this file to discover all available pages before exploring further.

本页内容由 AI 自动翻译,所有内容以英文版本为准。查看英文版 →
Solana 的账户模型是阅读 Raydium 代码前最重要的理解概念。与以太坊中状态与合约代码并存不同,Solana 程序是完全无状态的:所有状态都存在于独立的”账户”中,程序对这些账户进行操作。每一个 Raydium 流动性池、头寸和金库都是一个账户 — 理解这些账户的工作原理能让其余文档更容易理解。

基本划分:程序与账户

程序

Solana 上的程序是可执行代码 — 从文件加载的编译二进制文件,部署到一个 Pubkey,通过交易调用。程序没有关联状态;它们只包含逻辑。 Raydium 的程序:
  • CPMM:CPMMoo8L3F4NbTegBCKVNunggL7H1Zpdmwpwh8KMoZ0F
  • CLMM:CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK
  • AMM v4:675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8
每一个都是固定的二进制文件。程序在调用之间不”记得”任何东西。

账户

一个账户是链上的一行数据。每个账户都有:
  • pubkey — 它的地址。
  • owner — 拥有它的程序(控制写入)。
  • data — 原始字节。
  • lamports — SOL 余额(1 SOL = 1,000,000,000 lamports)。
  • rent_epoch — 遗留的租金收取字段(自租金豁免成为强制要求后已忽略)。
当你查询 Raydium 流动性池时,你在读一个或多个账户。当执行交换时,CPMM 程序读写多个账户 — 池状态、金库、观察状态和用户代币账户。

所有权

每个账户恰好由一个程序拥有。只有该程序的代码可以修改账户的 data 字段。用户可以修改他们可以签名的账户上的 lamports(发送/接收 SOL),但修改 data 需要所有者程序代表他们执行。 例子:
  • 你的用户钱包:由系统程序拥有。Lamports 存放在这里;你签署以转账。
  • 你的 USDC 代币账户:由 SPL Token 程序拥有。Token 程序的 transfer 指令更新余额。
  • Raydium 池状态账户:由 CPMM 程序拥有。只有 CPMM 的指令可以修改储备、费用等。
  • Raydium 头寸 NFT 的 PersonalPositionState:由 CLMM 程序拥有。
“由…拥有”是严格的:如果程序 A 写入由程序 B 拥有的账户,Solana 运行时会拒绝该交易。

租金与租金豁免

创建账户消耗存储空间。Solana 对这个空间收取租金,但自 2020 年以来所有新账户都必须是租金豁免的 — 意味着它们持有足够的 lamports 来预付 2 年内需要支付的租金。实际上:
  • 租金豁免账户永久存在。
  • 关闭账户会将 lamports 返回给关闭签署者。
对于 165 字节账户(例如,SPL Token 账户),租金豁免约为 0.00204 SOL。对于 1,440 字节的 Raydium CPMM 池状态,约为 0.011 SOL。

Raydium 租金成本

账户大小租金
CPMM PoolState~1,440 B~0.011 SOL
CLMM PoolState~1,500 B~0.012 SOL
CLMM TickArray~9,000 B~0.063 SOL
CLMM PersonalPositionState~280 B~0.003 SOL
ATA165 B~0.002 SOL
Vault(代币账户)165 B~0.002 SOL
创建池需要同时为多个账户支付租金 — 这就是为什么 CPMM 池创建总共费用约 0.15 SOL。

数据与可执行账户

账户有两种类型:

数据账户

保存状态(池储备、代币余额、用户头寸)。executable = false。这是绝大多数。

可执行账户

保存程序字节码。executable = true。这些是程序(CPMM、CLMM 等)。程序除了字节码外没有其他数据。

程序衍生账户(PDA)

PDA 是一个数据账户,其地址是从程序和一些种子确定性衍生的 — 这个地址不存在私钥。只有衍生程序可以通过 invoke_signed 代表 PDA 签署。 Raydium 广泛使用 PDA:
  • 池状态 PDA:从 [poolTypeDiscriminator, mintA, mintB, ammConfig] 衍生。
  • 金库 PDA:从 [pool, mint] 衍生。
  • 观察状态 PDA:从 [observationSeed, pool] 衍生。
PDA 让 Raydium 在无需管理密钥的情况下在可预测的地址创建账户。任何人都可以给定种子为已知池计算 PDA 地址。 solana-fundamentals/pdas-and-cpis

交易与账户引用

每个 Solana 交易都携带一个明确的账户列表,它将读写这些账户。运行时强制执行:
  • 列出的账户可以根据其 is_writable 标志进行读取或写入。
  • 未列出的账户无法被触及。
对于 Raydium 交换,交易的账户列表包括:
[readonly] CPMM program
[writable] pool state
[readonly] amm config
[readonly] pool authority (PDA)
[writable] input vault
[writable] output vault
[writable] user input ATA
[writable] user output ATA
[readonly] input mint
[readonly] output mint
[readonly] input token program
[readonly] output token program
[writable] observation state
[signer,writable] user
这种明确的枚举是 Solana 交易快速且可并行化的原因 — 运行时可以提前确定非冲突的交易。

账户大小与数据布局

每个 Raydium 账户都有固定或有界的大小。布局在代码中定义(带 #[repr(C)] 的 Rust 结构体)并在 sdk-api/anchor-idl 中记录。 Anchor 程序为它们创建的每个账户前置一个 8 字节的鉴别器,从 hash("account:<StructName>")[0..8] 衍生。这让客户端只需读取前 8 个字节就能识别账户类型 — 对于枚举所有特定类型账户的 getProgramAccounts 扫描至关重要。

读取 Raydium 池状态

通过 SDK:
const pool = await raydium.cpmm.getPoolInfoFromRpc({ poolId });
console.log(pool.poolInfo);
通过原始 RPC + 布局:
const accountInfo = await connection.getAccountInfo(poolId);
const data = accountInfo.data;
// Skip first 8 bytes (discriminator), then parse according to struct layout.
const poolState = CpmmPoolStateLayout.decode(data.slice(8));
布局在 SDK 源中的 src/raydium/cpmm/layout.ts 中。

实际例子:读取代币账户

让我们读一个用户的 USDC 余额。
import { Connection, PublicKey } from "@solana/web3.js";
import { getAssociatedTokenAddressSync, AccountLayout } from "@solana/spl-token";

const connection = new Connection("https://api.mainnet-beta.solana.com");
const user       = new PublicKey("YourUserWallet...");
const usdcMint   = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");

// 1. Compute the ATA address (PDA-style derivation).
const ata = getAssociatedTokenAddressSync(usdcMint, user);

// 2. Read the account.
const accountInfo = await connection.getAccountInfo(ata);
if (!accountInfo) {
  console.log("ATA doesn't exist yet (user has never held USDC).");
  return;
}

// 3. Verify owner is SPL Token program.
console.assert(accountInfo.owner.toBase58() === "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");

// 4. Decode the data.
const parsed = AccountLayout.decode(accountInfo.data);
console.log("Balance (smallest units):", parsed.amount.toString());
console.log("Mint:",                     new PublicKey(parsed.mint).toBase58());
console.log("Owner:",                    new PublicKey(parsed.owner).toBase58());
这种模式 — 衍生地址、获取账户、验证所有者、解码 — 适用于每一个链上读取,包括 Raydium 池。

这对 Raydium 的重要性

账户模型塑造了 Raydium 的设计:
  • 池状态是单个账户 — 关于池的所有内容(代币、储备、费用、管理员)都存在于一个由池程序拥有的账户中。
  • LP 代币是标准 SPL 代币账户 — Raydium 将代币化委托给 SPL Token 程序。
  • Tick 数组是分块的 — CLMM 不能有单个可增长的 tick 数组,因为账户有固定分配的大小;它使用分块的 TickArray PDA。
  • 头寸 NFT 是 Metaplex NFT — CLMM 头寸是按 Metaplex 规范的标准 NFT;头寸状态是一个单独的 PDA。
理解这一点让你能正确回答”X 在哪里”的问题:
  • “池储备在哪里?” → 两个金库账户(代币账户)由 SPL Token 程序拥有,权限委托给池程序的 PDA。
  • “CLMM 的 tick 数据在哪里?” → 一系列 TickArray PDA,每一个覆盖 60 个连续 tick。
  • “我的农场质押在哪里?” → 一个 UserLedger PDA,从 [user, farmId] 衍生,由农场程序拥有。

参考资源

来源: