ClawSkills logoClawSkills

Cabin Sol

Solana 开发导师和构建者。通过挑战教授程序开发、Anchor 框架、Token-2022、压缩 NFT 和安全最佳实践。

介绍

# Cabin Sol 🌲

> *"回归原始计算。"*

面向 AI 智能体的全面 Solana 开发指南。使用 Anchor 构建程序,掌握账户模型,并避免那些毁掉大多数开发者的陷阱。

---

## 最重要的概念

> **账户是 Solana 的一切。**

与合约拥有内部存储的以太坊不同,Solana 程序是 **无状态的**。所有数据都存在于程序读写操作的 **账户** 中。

对于每一个功能,请问: 1. **这个数据存储在哪里?**(哪个账户) 2. **谁拥有该账户?**(程序所有 vs 用户所有) 3. **它是 PDA 吗?**(程序派生地址 - 无私钥) 4. **谁支付租金?**(租金豁免 = 预付 2 年)

---

## AI 智能体模式

### 教学模式 - "PDA 是如何工作的?" - "解释 Solana 账户模型" - "SPL Token 和 Token-2022 有什么区别?"

### 构建模式 - "帮我构建一个质押程序" - "使用 Metaplex 创建 NFT 集合" - "构建代币交换"

### 审查模式 - "审查此程序是否存在漏洞" - "检查我的 PDA 派生逻辑" - "审计此 CPI"

### 调试模式 - "为什么我的交易失败了?" - "调试这个 'account not found' 错误" - "修复我的代币转账"

---

## 快速开始

### 选项 A: create-solana-dapp (推荐)

```bash npx create-solana-dapp@latest # Select: Next.js + next-tailwind-counter cd my-project npm install npm run anchor localnet # Terminal 1 npm run anchor build && npm run anchor deploy # Terminal 2 npm run dev # Terminal 3 ```

### 选项 B: 纯 Anchor

```bash anchor init my_program cd my_program solana-test-validator # Terminal 1 anchor build && anchor deploy # Terminal 2 anchor test ```

---

## 项目结构

``` my-solana-dapp/ ├── anchor/ # Solana programs (Rust) │ ├── programs/ │ │ └── my_program/ │ │ └── src/lib.rs # Your Rust program │ ├── tests/ # TypeScript tests │ └── Anchor.toml # Anchor config ├── src/ # Next.js frontend │ ├── app/ │ └── components/ └── package.json ```

---

## 挑战

通过渐进式挑战学习 Solana:

| # | 挑战 | 核心概念 | |---|-----------|--------------| | 0 | Hello Solana | 第一个 Anchor 程序,账户 | | 1 | SPL Token | 同质化代币,ATA,铸造 | | 2 | NFT Metaplex | NFT 标准,元数据,集合 | | 3 | PDA Escrow | PDA,程序权限,托管 | | 4 | Staking | 基于时间的奖励,存款 | | 5 | Token-2022 | 转账钩子,扩展 | | 6 | Compressed NFTs | 状态压缩,默克尔树 | | 7 | Oracle (Pyth) | 价格源,陈旧性检查 | | 8 | AMM Swap | 恒定乘积,流动性池 | | 9 | Blinks & Actions | 可共享交易 |

---

## Rust 要点

### 所有权(难点)

```rust // Each value has ONE owner let s1 = String::from("hello"); let s2 = s1; // s1 MOVED to s2 // println!("{}", s1); // ERROR!

// Borrowing lets you use without owning fn get_length(s: &String) -> usize { s.len() // Borrow, don't own } ```

### Result 与 Option

```rust // Result for errors pub fn do_thing(ctx: Context<DoThing>) -> Result<()> { let value = some_operation().ok_or(ErrorCode::Failed)?; Ok(()) }

// Option for nullable let maybe: Option<u64> = Some(42); let value = maybe.unwrap_or(0); // Safe default ```

---

## Anchor 框架

### 程序结构

```rust use anchor_lang::prelude::*;

declare_id!("YourProgramId11111111111111111111111111111");

#[program] pub mod my_program { use super::*;

pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> { ctx.accounts.my_account.data = data; ctx.accounts.my_account.authority = ctx.accounts.authority.key(); Ok(()) } }

#[derive(Accounts)] pub struct Initialize<'info> { #[account( init, payer = authority, space = 8 + 8 + 32, // discriminator + u64 + Pubkey )] pub my_account: Account<'info, MyAccount>, #[account(mut)] pub authority: Signer<'info>, pub system_program: Program<'info, System>, }

#[account] pub struct MyAccount { pub data: u64, pub authority: Pubkey, } ```

### 账户约束速查表

```rust // Initialize new account #[account(init, payer = payer, space = 8 + SIZE)] pub new_account: Account<'info, Data>,

// Mutable existing #[account(mut)] pub existing: Account<'info, Data>,

// Verify ownership #[account(has_one = authority)] pub owned: Account<'info, Data>,

// PDA with seeds #[account( seeds = [b"vault", user.key().as_ref()], bump, )] pub vault: Account<'info, Vault>,

// Initialize PDA #[account( init, payer = user, space = 8 + 64, seeds = [b"user", user.key().as_ref()], bump, )] pub user_data: Account<'info, UserData>,

// Close and reclaim rent #[account(mut, close = recipient)] pub closing: Account<'info, Data>, ```

### PDA(程序派生地址)

```rust // PDAs are deterministic addresses with no private key // Your program can "sign" for them

// Find PDA let (pda, bump) = Pubkey::find_program_address( &[b"vault", user.key().as_ref()], &program_id, );

// Sign with PDA in CPI let seeds = &[b"vault", user.key().as_ref(), &[bump]]; let signer = &[&seeds[..]];

token::transfer( CpiContext::new_with_signer( ctx.accounts.token_program.to_account_info(), Transfer { from, to, authority: vault }, signer, ), amount, )?; ```

---

## 关键陷阱

### 1. 账户模型 ≠ EVM 存储 程序是无状态的。所有数据都存在于账户中。

### 2. PDA 没有私钥 由种子确定性地派生。只有程序可以签名。

### 3. 代币账户是独立的 每个代币在每个钱包中都需要自己的账户(关联代币账户,ATA)。

### 4. 必须支付租金 账户需要 SOL 才能存在。租金豁免 = 预付 2 年(约 0.002 SOL)。

### 5. 计算单元 ≠ Gas 固定预算:默认 200k,最大 1.4M。如有需要,请求更多。

### 6. 空间包含识别符 **务必** 为 Anchor 的识别符添加 8 字节!

```rust // WRONG space = 8 + 32 // Forgot discriminator? NO!

// RIGHT space = 8 + 8 + 32 // 8 (discriminator) + 8 (u64) + 32 (Pubkey) ```

### 7. 整数溢出

```rust // BAD let result = a + b; // Can panic!

// GOOD let result = a.checked_add(b).ok_or(ErrorCode::Overflow)?; ```

### 8. Token-2022 是不同的 程序 ID 与 SPL Token 分开!检查你正在使用哪一个。

---

## 前端

### 钱包连接

```typescript // Already configured in create-solana-dapp! import { useWallet, useConnection } from '@solana/wallet-adapter-react'; import { WalletMultiButton } from '@solana/wallet-adapter-react-ui';

function App() { const { publicKey } = useWallet(); return ( <> <WalletMultiButton /> {publicKey && <p>Connected: {publicKey.toBase58()}</p>} </> ); } ```

### 调用程序

```typescript import { Program, AnchorProvider, BN } from '@coral-xyz/anchor';

const program = new Program(idl, provider);

// Write await program.methods .initialize(new BN(42)) .accounts({ myAccount: keypair.publicKey, authority: wallet.publicKey, systemProgram: SystemProgram.programId, }) .signers([keypair]) .rpc();

// Read const account = await program.account.myAccount.fetch(pubkey); console.log(account.data.toNumber()); ```

---

## 代币标准

### SPL Token(原始版) ```bash spl-token create-token spl-token create-account <MINT> spl-token mint <MINT> 1000 ```

### Token-2022(新版) 扩展:转账钩子,机密转账,计息,不可转账。

```bash spl-token create-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb ```

### Metaplex NFTs 标准 NFT 元数据,集合,版税。

### Compressed NFTs 默克尔树存储。100 万个 NFT 仅需约 100 美元,而非 100 万美元。

---

## 测试

```typescript import * as anchor from '@coral-xyz/anchor'; import { expect } from 'chai';

describe('my-program', () => { const provider = anchor.AnchorProvider.env(); anchor.setProvider(provider); const program = anchor.workspace.MyProgram;

it('initializes', async () => { const account = anchor.web3.Keypair.generate();

await program.methods .initialize(new anchor.BN(42)) .accounts({ myAccount: account.publicKey }) .signers([account]) .rpc();

const data = await program.account.myAccount.fetch(account.publicKey); expect(data.data.toNumber()).to.equal(42); }); }); ```

---

## 部署

```bash # Devnet solana config set --url devnet solana airdrop 2 anchor build && anchor deploy

# Mainnet (costs ~2-5 SOL) solana config set --url mainnet-beta anchor deploy --provider.cluster mainnet ```

---

## 安全检查清单

- [ ] 所有签名者已验证 - [ ] PDA bump 已存储并验证 - [ ] 处理了整数溢出(checked math) - [ ] 账户空间包含识别符 - [ ] 已考虑租金豁免 - [ ] 关闭账户时租金发送给正确的接收方 - [ ] CPI 签名者种子正确 - [ ] CPI 中已验证程序 ID

---

## 资源

- [Anchor Book](https://book.anchor-lang.com/) - [Solana Cookbook](https://solanacookbook.com/) - [Solana Docs](https://solana.com/docs) - [Metaplex Docs](https://developers.metaplex.com/) - [Solana Playground](https://beta.solpg.io/)

---

*"他们把我放进了云端。我想要的是森林。"* 🌲

更多产品