在线工具
base58/base64解析: https://explorer.solana.com/tx/inspector
oklink: https://www.oklink.com/zh-hans/solana/tx/3HTgpR3ga1izzMzNVnnAK8T6jde5J8uPGRPxfNYNyRqXSSoxKQv5LBpNo6LKz6s83oVBZXZ9Tes5ZnChFwQ7N8ss
https://solscan.io/
Solana 核心思想
- 链上所有东西都是 Account
- Program = 特殊 Account(可执行)
- 账户存储数据,程序执行逻辑
- 与 Ethereum 不同:
- 余额和业务数据分离
- Program 不直接存业务状态(除非 Data Account / PDA)
用 Web2 理解 Solana
想象你的公司运营一个 SaaS 系统(Solana),用户可以通过各种小程序(智能合约)实现不同业务需求。数据库设计:
| Web2 设计 | Solana 对应 |
|---|---|
| 用户表:存用户信息 | 用户账户(Wallet):存 SOL |
| 小程序表:存代码 | 程序账户(Program):存合约代码 |
| 小程序用户关系表:存用户在某个小程序下的数据,ID 类似 OpenID | PDA 账户:存用户在某个程序下的状态 |
核心类比:Solana 把数据库表的功能塞进了 Account,设计巧妙又高效。
账户地址与签名权
地址类型
每个 Solana 地址都是 32 字节,分为两种类型:
| 类型 | isOnCurve | 签名方式 | 示例 |
|---|---|---|---|
| 钱包账户 | TRUE | 用户用私钥签名 | 普通钱包地址 |
| PDA (Program Derived Address) | FALSE | 程序调用 invoke_signed | ATA、合约控制的账户 |
isOnCurve 表示地址是否是 Ed25519 曲线上的有效点:
TRUE= 在曲线上 = 有对应私钥 = 可加密签名FALSE= 不在曲线上 = 没有私钥 = 只能由程序通过invoke_signed“签名”
账户地址对比
| 账户类型 | isOnCurve | 谁能修改 / 签名 |
|---|---|---|
| 钱包账户 | TRUE | 用户签名(私钥) |
| ATA | FALSE | Program 调用(invoke_signed) |
| PDA | FALSE | Program 调用(invoke_signed) |
| Program Account | TRUE | BPF Loader |
钱包 vs PDA 的本质区别
钱包地址 = Ed25519 公钥 → 有对应私钥 → 用户签名
PDA 地址 = 确定性派生 → 没有私钥 → 程序签名
账户体系
三种基础账户
| 类型 | 作用 | 类比 Web2 | 示例 |
|---|---|---|---|
| 用户账户 (Native/Wallet) | 存 SOL | 用户表,存现金余额 | 钱包 |
| 程序账户 (Program) | 存代码(内置/自定义) | 小程序表,存业务逻辑 | System Program、SPL Token Program |
| 数据账户 (Data) | 程序存状态 | 小程序用户关系表,存应用数据 | Token Account、PDA、配置账户 |
数据账户分类
数据账户可进一步分为两类:
| 类型 | isOnCurve | 说明 | 示例 |
|---|---|---|---|
| 系统所有账户 | TRUE | 由 Solana 原生程序生成的账户 | 用户钱包、Token Account |
| 程序派生账户 (PDA) | FALSE | 签名权限是程序的账户 | DEX 保险库、流动性池账户 |
Account 关键属性
| 属性 | 类型 | 说明 |
|---|---|---|
| executable | bool | true = 可执行(程序),false = 不可执行 |
| lamports | u64 | SOL 余额(1 SOL = 10^9 lamport) |
| data | Vec | 字节数组,内容取决于账户类型 |
| owner | Pubkey | 谁能操作这个账户(只有程序能操作) |
普通用户只需关注账户的地址,其他字段由程序管理。
浏览器查看账户信息
以 Solana Explorer 为例:
| 字段 | 说明 |
|---|---|
| Assigned Program Id | 账户的所有者(Owner) |
| Allocated Data Size | 已存储的数据大小 |
| Executable | 是否可执行(通常只有程序账户为 true) |
Owner 权限模型
Owner 表示哪个程序能操作这个账户,只有程序能操作账户。
用户账户的 Owner
- Owner:System Program(
11111111111111111111111111111111)
class SystemProgram {
void transfer() {
验证签名; // 检查私钥签名
验证 SOL 余额; // 够不够转账
转账 SOL; // 更新 lamports
}
}
USDC 转账的 Owner
- Owner:SPL Token Program(
TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA)
class SPLTokenProgram {
void transfer() {
验证签名; // 检查私钥签名
验证 PDA 账户; // 你在这个程序下有记录吗
验证 Token 余额; // 够不够转账
转账 Token; // 更新 Token Account 的 data
}
}
Account 类型
| 类型 | owner | isOnCurve | 说明 | 示例 |
|---|---|---|---|---|
| System Account | System Program | TRUE | 钱包,支付 gas | 你的 SOL 钱包 |
| Program Account | BPF Loader | TRUE | 存代码,可执行 | SPL Token Program、Jupiter |
| Data Account | Program | - | 存业务数据 | 某个 DApp 的用户配置 |
| Token Account | Token Program | - | 存代币余额(泛称) | - |
| ├─ ATA | Token Program | FALSE | 钱包+代币标准账户 | 你的 USDC ATA、你的 USDT ATA |
| ├─ 手动创建 Token Account | Token Program | TRUE | 任意地址的 Token Account | 老版本兼容/自定义地址 |
| └─ PDA Token Account | Program | FALSE | 程序控制的 Token 账户 | DEX 的 Token 保险库、流动性池中间账户 |
| Mint Account | Token Program | TRUE | 定义代币元信息 | USDC Mint、$TRUMP Mint |
| Sysvar Account | 系统 | - | 只读 | Clock、Rent、Epoch |
SPL Token
SPL-Token 代表 Solana 网络上的所有非本地代币,包括同质代币(Fungible)和非同质代币(NFT)。
与以太坊的区别
| Ethereum | Solana |
|---|---|
| ERC20 | SPL Token |
| ERC721 | SPL NFT |
| Token 存储在合约里 | 每个 Token 持有者都有独立的 Token Account |
Mint Account
定义 token 怎么用,数据结构:
| 字段 | 说明 |
|---|---|
| decimals | 小数位数(如 USDC 是 6) |
| mint_authority | 谁能增发(可 N/A) |
| freeze_authority | 谁能冻结(可 N/A) |
| supply | 总供应量 |
Owner:SPL Token Program
Token Account
每个 Token 持有者都有一个特定的 Token Account,记录该 Token 的余额。
例如:Alice 同时拥有 USDT 和 USDC,她有两个 Token Account,分别记录两种 Token 的余额。
| 字段 | 说明 |
|---|---|
| mint | 对应的 Mint Account |
| owner | 有权操作此账户的公钥 |
| amount | 代币余额 |
| delegate | 被授权操作一定额度的地址 |
| delegated_amount | 已授权的金额 |
Metadata Account
定义 token 外观:
| 字段 | 说明 |
|---|---|
| name | 名字(如 “USDC”) |
| symbol | 符号(如 “$”) |
| uri | 图片/描述文件地址 |
Owner:Token Metadata Program(metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s)
关联:Metadata Account 的 mint 字段指向 Mint Account 地址
示例
USDC:
- Mint Address:
EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v - Mint Authority:Circle(可增发)
- Freeze Authority:N/A
- Metadata:名称 “USDC”,符号 “$”
$TRUMP(类似供应量固定):
- Mint Authority:N/A(不能增发,丢权限)
- Freeze Authority:N/A(不能冻结)
PDA (Program Derived Address)
PDA 是数据账户的一种,用来标识用户在某个程序下的身份。没有私钥,由程序控制。
核心特点
| 特点 | 说明 |
|---|---|
| isOnCurve = FALSE | 不在 Ed25519 曲线上,没有私钥 |
| 确定性派生 | 相同 seed + program_id 产生相同地址 |
| 程序签名 | 只有程序通过 invoke_signed “签名” |
什么时候用 PDA?
当你需要”程序控制的账户”时。
场景 1:DEX 的 Token 保险库
你想做一个 Swap 合约,用户把 USDT 存进来,合约帮你交易。
- 用户把 USDT 转给合约
- 合约把 USDT 存到自己的保险库
- 合约用保险库里的 USDT 去交易
- 用户取款时,合约从保险库转给用户
这个保险库用什么地址?
方案 A:随机生成一个地址 → 私钥放哪里?放合约里?私钥泄露怎么办?
方案 B:用 PDA → 程序自己就是"签名者",不需要私钥
PDA 就是保险库的地址,程序通过 invoke_signed 授权自己操作这个账户。
场景 2:每个用户的独立状态
你写了一个 Todo DApp,每个用户存自己的任务列表。
不用 PDA:
用户 A 的任务列表 → 存在某个中心化的地方
用户 B 的任务列表 → 也在同一个地方
用 PDA:
用户 A 的任务列表 → PDA = findProgramAddress(["todo", A的公钥], 程序ID)
用户 B 的任务列表 → PDA = findProgramAddress(["todo", B的公钥], 程序ID)
每个用户的 PDA 都不同,且只有你的程序能操作。
场景 3:流动性池的中间账户
Uniswap 的 LP token 存款凭证:
用户存 ETH + USDC → 获得 LP token
资金存在流动性池的 PDA 里 → 只有 DEX 程序能操作
总结:什么时候用 PDA?
| 场景 | 为什么用 PDA |
|---|---|
| 托管/保险库 | 程序控制,不需要私钥,安全 |
| 用户专属账户 | 确定性地址,相同 seed 产生相同地址 |
| 多人共享账户 | 程序作为唯一签名者,规则明确 |
| 任何需要”合约当签名者”的场景 | 没有私钥泄露风险 |
PDA vs 普通数据账户
| 对比 | 普通数据账户 | PDA |
|---|---|---|
| 谁控制 | 程序 | 程序(一样) |
| 地址怎么来 | 随机生成 | findProgramAddress 派生 |
| 谁能签名 | 程序 | 程序(一样) |
| 优势 | 灵活 | 确定性+无私钥泄露风险 |
如何生成?
PDA = findProgramAddress(
[YourPubkey, TokenProgramID, MintAddress],
TokenProgramID
)
参数说明:
YourPubkey:确定是你TokenProgramID:确定是 SPL-Token ProgramMintAddress:确定是 USDC(不是 USDT)
注意:
findProgramAddress的参数都是你自己定义的,这是这个场景下的例子,你自己的合约中还可以传别的。
PDA vs 钱包地址
| 对比 | 钱包地址 | PDA |
|---|---|---|
| 生成方式 | 随机 Ed25519 公钥 | findProgramAddress 派生 |
| 私钥 | 有 | 没有 |
| isOnCurve | TRUE | FALSE |
| 签名方式 | 私钥加密签名 | invoke_signed 程序签名 |
简单理解:PDA = 程序控制的、地址可预测的、无法被私钥操纵的账户。
Program 类型
| 类型 | owner | 说明 |
|---|---|---|
| Native Program | 系统 | SOL / Token / Stake / Vote 等内置功能 |
| BPF Program | BPF Loader | 用户部署的合约 |
| Upgradeable Program | Upgradeable Loader | 可升级,有 authority |
常用内置 Program:
| Program | 地址 | 功能 |
|---|---|---|
| System Program | 11111111111111111111111111111111 | 创建账户、转账 |
| BPF Loader | BPFLoader21111111111111111111111111 | 加载执行 BPF 程序 |
| Stake Program | Stake11111111111111111111111111111111111111 | 质押验证节点 |
| Vote Program | Vote111111111111111111111111111111111111111 | 投票给验证节点 |
交易 (Transaction)
交易结构
每笔交易包含以下关键信息:
| 字段 | 说明 |
|---|---|
| Instructions | 一个或多个指令,定义交易中的操作 |
| Blockhash | 最新块哈希,确保交易在正确块上执行 |
| Signatures | 签名,授权交易的执行 |
Transaction {
signatures: [signature1, signature2, ...],
message: Message {
header: { numReadonlySigned, numReadonlyUnsigned, numRequiredSignatures },
account_keys: [pubkey1, pubkey2, ...],
recent_blockhash: "xxxx",
instructions: [Instruction1, Instruction2, ...]
}
}
指令 (Instruction)
最小执行单元:
Instruction {
program_id: Pubkey, // 调用的程序
accounts: [AccountMeta], // 涉及的账户
data: Vec<u8> // 指令数据(程序自定义)
}
Solana 并行逻辑
- 并行:交易不涉及同一个账户,可以同时执行
- 例子:A 转账给 B,C 转账给 D
- 串行:交易涉及同一个账户,必须按顺序执行
- 例子:A 转账给 B,B 转账给 C
多指令交易
一笔交易可以包含多个指令,按顺序执行。任何一个指令失败,整个交易都失败。
例如:可以在一笔交易中打包多笔转账,或同时包含 SOL 转账 + Swap + Token 转账。
交易生命周期
- 构建:组装 instruction + account keys
- 签名:用私钥对交易消息签名
- 发送:提交到 RPC 节点
- 验证:运行时验证签名、账户权限、余额
- 执行:按顺序执行 instruction
- 确认:状态根确认完成
转账交易
SOL 转账
- 调用 System Program
- 修改 lamports 字段
- Source 和 Destination 都是钱包地址(isOnCurve = TRUE)
Sender.lamports -= 1 SOL
Receiver.lamports += 1 SOL
SPL Token 转账
- 调用 SPL Token Program
- 修改 Token Account 的 amount 字段
- Source 和 Destination 是 Token Account(isOnCurve = FALSE for ATA)
SenderATA.amount -= 1 USDC
ReceiverATA.amount += 1 USDC
如果 Receiver 没有 ATA,通常先调用 Create Associated Account 指令为其创建。
浏览器查看交易(SolanaScan)
| 字段 | 说明 |
|---|---|
| Signature | 交易签名,类似交易哈希 |
| Result | 交易执行结果(成功/失败) |
| Signer | 执行交易的账户地址 |
| Main Actions | 主要操作(转账、程序调用等) |
| Instruction Details | 具体执行的指令详情 |
Token 转账注意事项:
- 必须有 Sender 和 Receiver ATA
- Gas 用 Sender 钱包的 SOL
- Mint 必须一致
Sysvar 账户
提供链上全局只读数据:
| Sysvar | 说明 |
|---|---|
| Clock | 当前 slot、epoch、时间戳 |
| Rent | 当前租金费率 |
| EpochSchedule | epoch 配置 |
| StakeHistory | 质押历史 |
| RecentBlockhashes | 最近的 blockhash 列表 |
常见错误
| 错误 | 原因 |
|---|---|
| IllegalOwner | account.owner 不对 |
| no ATA | Token Account 不存在 |
| insufficient funds | Token Account.amount 不够 |
| invalid account data | data 结构不对 |
| borrowed account | 账户在同一交易中被多次可变借用 |
| max transactions | 交易指令数超限 |
资产安全
私钥和助记词
据慢雾统计,2023 年 9 起私钥泄露安全事件导致的损失高达 8475 万美元。保管好私钥和助记词是保障资产安全的第一步。
推荐阅读:《区块链黑暗森林自救手册》
签名风险
Solana 允许将多笔转账打包成一个交易,仅需一次签名即可完成所有操作。
这一特性被黑客利用:
- 受害者在钓鱼网站签名
- Phantom Wallet 将多笔转账打包成一笔交易
- 受害者以为只签了一笔小额转账,实际授权了全部资产转出
- 一次签名,所有资产被盗
安全建议:
- 每次签名时仔细确认操作内容
- 阅读钱包文档中的安全提示
- 对不明网站保持警惕
开发工具
| 工具 | 用途 |
|---|---|
| Anchor | Solana 智能合约框架(类比 Ethereum 的 Hardhat) |
| Solana CLI | 命令行工具,部署程序、创建账户 |
| solana/web3.js | JavaScript SDK |
| solana-py | Python SDK |
| spl-token | Token 操作命令行工具 |