9_Solana-Anchor安装

搭建本地开发环境

https://solana.com/developers/guides/getstarted/setup-local-development

  • 安装Anchor
    • avm: Anchor Version Manager
1
2
3
4
5
6
7
8
9
10
11
12
13
sudo apt-get update && sudo apt-get upgrade && sudo apt-get install -y pkg-config build-essential libudev-dev libssl-dev

cargo install --git https://github.com/coral-xyz/anchor avm --locked --force

# 安装最新版
avm install latest

# 使用最新版
avm use latest

# check the version

anchor --version
  • Setup a localhost blockchain cluster

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    solana-test-validator --help

    # setup localhost blockchain
    solana-test-validator


    # swith to localhost
    solana config set --url localhost

    solana config get

    # set default wallet
    solana config set -k ~/.config/solana/id.json

    # get the airdrop from localhost blockchain
    solana airdrop 2

    # get balance
    solana balance
  • 新建anchor项目
1
anchor init <new-workspace-name>

Anchor程序结构

https://www.solanazh.com/course/7-3

Anchor官方示例:

一个Anchor工程主要包含:

  • “declare_id”宏声明的合约地址,用于创建对象的owner
  • #[derive(Accounts)] 修饰的Account对象,用于表示存储和指令, 包含了指令执行所要用到的账户
  • “program” 模块,这里面写主要的合约处理逻辑

对应到我们之前的HelloWorld,就是要将state和instruction部分用 #[derive(Accounts)] 修饰,将process逻辑放到program模块中,并增加一个合约地址的修饰。

#[program] 修饰的Module即为指令处理模块。其中有一个Context类型,来存放所有的指令参数。比如

  • ctx.accounts 所有的请求keys,也就是AccountMeta数组
  • ctx.program_id 指令中的program_id
  • ctx.remaining_accounts 指令中,没有被下面说的”Accounts”修饰的成员的AccountMeta
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
use anchor_lang::prelude::*;

// 程序ID
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

// 指令执行逻辑
#[program]
mod basic_1 {
use super::*;

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

pub fn update(ctx: Context<Update>, data: u64) -> Result<()> {
let my_account = &mut ctx.accounts.my_account;
my_account.data = data;
Ok(())
}
}


// 包含了Initialize指令所需要的账户(my_account, user, system_program)
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = user, space = 8 + 8)]
pub my_account: Account<'info, MyAccount>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}

// 包含了Update指令所需要的账户(my_account, user, system_program)
#[derive(Accounts)]
pub struct Update<'info> {
#[account(mut)]
pub my_account: Account<'info, MyAccount>,
}

// 数据账户
#[account]
pub struct MyAccount {
pub data: u64,
}

8_Solana-原生程序

8_Solana程序开发(原生)

Hello world!

https://solana.com/developers/guides/getstarted/hello-world-in-your-browser

https://explorer.solana.com/tx/65AGdio7aeK47h9HjAvnR7ap8dFepm3Sp3MJcFvPfuSnXWHJgQBENtxe5xijEk1uLy8bRw31fyjyXZvoBEEQzMGW?cluster=devnet

https://explorer.solana.com/tx/5mX3oxvHZAXbYfnpjwDgEXJLTEgYXNrpKvhKufkVqay7Js3qbgaDu2P3ESWiQE5YjjaLGUur2PAJ4pzLPxzW1aUL?cluster=devnet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use solana_program::{
account_info::AccountInfo,
entrypoint,
entrypoint::ProgramResult,
pubkey::Pubkey,
msg,
};

// declare and export the program's entrypoint
entrypoint!(process_instruction);

// program entrypoint's implementation
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8]
) -> ProgramResult {
// log a message to the blockchain
msg!("Hello, world!");

// gracefully exit the program
Ok(())
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// create an empty transaction
const transaction = new web3.Transaction();

// add a hello world program instruction to the transaction
transaction.add(
new web3.TransactionInstruction({
keys: [],
programId: new web3.PublicKey(pg.PROGRAM_ID),
}),
);

// send the transaction to the Solana cluster
console.log("Sending transaction...");
const txHash = await web3.sendAndConfirmTransaction(
pg.connection,
transaction,
[pg.wallet.keypair],
);
console.log("Transaction sent with hash:", txHash);

7_Solana的Token

Solana的Token

https://solana.com/docs/core/tokens

关键点

  • Token代表了同质化和非同质化的资产的所有权
  • Token Program 包含了所有与token交互所需要的指令
  • Token Extensions Program 是新版程序,包含了额外的特性
  • Mint Account: 代表了一个唯一的代币
  • Token Account:
    • A Token Account tracks individual ownership of tokens for a specific mint account.
  • Associated Token Account (ATA): 由 owner地址 和 Mint Account地址派生出来的 Token Account
    • An Associated Token Account(ATA) is a Token Account created with an address derived from the owner’s and mint account’s addresses.

注意区别:Token Account 和 ATA

  • Token Account 是更加通用的,可以随机生成(由客户端生成)

  • ATA 是通过确定性算法生成出来的, 标准化的

    1
    2
    3
    4
    5
    6
    7
    8
    ATA = PDA(
    [
    owner_address,
    token_program_id,
    mint_address
    ],
    associated_token_program_id
    )
  • 问题: SPL Token的接收账户必须是ATA吗?

    • 答: 不是不是必须,用户可以自定义seedsbump生成 Token Account

Token Program

  • InitializeMint: 发行代币
    • Create a new mint account to represent a new type of token.
  • InitializeAccount: 创建ATA账号
    • Create a new token account to hold units of a specific type of token (mint).
  • MintTo: 增发代币。
    • Create new units of a specific type of token and add them to a token account. This increases the supply of the token and can only be done by the mint authority of the mint account.
  • Transfer: 转移Token
    • Transfer units of a specific type of token from one token account to another.

Mint Account

  • Supply: token 的总发行量
  • Decimals: 精度
  • Mint authority: 持有增发token权限账户, 可以增发token
  • Freeze authority: 持有冻结转移的账户, 即将某个用户账户”拉黑”
    • The account authorized to freeze tokens from being transferred from “token accounts”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pub struct Mint {
/// Optional authority used to mint new tokens. The mint authority may only
/// be provided during mint creation. If no mint authority is present
/// then the mint has a fixed supply and no further tokens may be
/// minted.
pub mint_authority: COption<Pubkey>,
/// Total supply of tokens.
pub supply: u64,
/// Number of base 10 digits to the right of the decimal place.
pub decimals: u8,
/// Is `true` if this structure has been initialized
pub is_initialized: bool,
/// Optional authority to freeze token accounts.
pub freeze_authority: COption<Pubkey>,
}

例如 USDC的Mint Account : https://explorer.solana.com/address/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v

Token Account

  • Mint: 执行特定的Mint Account
    • The type of token the Token Account holds units of
  • Owner: The account authorized to transfer tokens out of the Token Account
    • 该Token Account 的所有者,有权转移该Token Account上的token
    • 注意: AccountInfo中owner 和 AccountInfo Data中的owner 是不同的,前者是Program的owner(即Token Program地址, 所有Token Account的owner都是 Token Program), 后者是 Token Account的所有者(即用户的钱包地址)

      原文: Note that each Token Account’s data includes an owner field used to identify who has authority over that specific Token Account. This is separate from the program owner specified in the AccountInfo, which is the Token Program for all Token Accounts.

  • Amount: Units of the token the Token Account currently holds
    • 余额
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pub struct Account {
/// The mint associated with this account
pub mint: Pubkey,
/// The owner of this account.
pub owner: Pubkey,
/// The amount of tokens this account holds.
pub amount: u64,
/// If `delegate` is `Some` then `delegated_amount` represents
/// the amount authorized by the delegate
pub delegate: COption<Pubkey>,
/// The account's state
pub state: AccountState,
/// If is_native.is_some, this is a native token, and the value logs the
/// rent-exempt reserve. An Account is required to be rent-exempt, so
/// the value is used by the Processor to ensure that wrapped SOL
/// accounts do not drop below this threshold.
pub is_native: COption<u64>,
/// The amount delegated
pub delegated_amount: u64,
/// Optional authority to close the account.
pub close_authority: COption<Pubkey>,
}

Associated Token Account

https://solana.com/docs/core/tokens#associated-token-account

  • Associated Token Account (ATA) 是一个 Token Account, 这个token account的地址是确定的,通过 owner的地址 和 Mint Account的地址一起生成出来的。你可以认为ATA就是每个用户的默认Token Account

    原文:An Associated Token Account is a token account whose address is deterministically derived using the owner’s address and the mint account’s address. You can think of the Associated Token Account as the “default” token account for a specific mint and owner.

获取 ATA

1
2
3
4
5
6
7
import { getAssociatedTokenAddressSync } from "@solana/spl-token";

const associatedTokenAccountAddress = getAssociatedTokenAddressSync(
USDC_MINT_ADDRESS,
OWNER_ADDRESS,
);

或者,通过PDA的方式生成 ATA

1
2
3
4
5
6
7
8
9
10
import { PublicKey } from "@solana/web3.js";

const [PDA, bump] = PublicKey.findProgramAddressSync(
[
OWNER_ADDRESS.toBuffer(),
TOKEN_PROGRAM_ID.toBuffer(),
USDC_MINT_ADDRESS.toBuffer(),
],
ASSOCIATED_TOKEN_PROGRAM_ID,
);

对于每个代币(即Mint Account), 每个钱包账户都有一个自己的 Token Account(也可以叫ATA), 如下图:

创建Token Metadata

需要使用Token Extensions Program

https://solana.com/docs/core/tokens#create-token-metadata

1
2
spl-token create-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
--enable-metadata
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pub struct TokenMetadata {
/// The authority that can sign to update the metadata
pub update_authority: OptionalNonZeroPubkey,
/// The associated mint, used to counter spoofing to be sure that metadata
/// belongs to a particular mint
pub mint: Pubkey,
/// The longer name of the token
pub name: String,
/// The shortened symbol for the token
pub symbol: String,
/// The URI pointing to richer metadata
pub uri: String,
/// Any additional metadata about the token as key-value pairs. The program
/// must avoid storing the same key twice.
pub additional_metadata: Vec<(String, String)>,
}

更多细节:https://solana.com/developers/guides/token-extensions/metadata-pointer

5_Solana的PDA账户

Solana PDA

https://solana.com/docs/core/pda

关键点

![](https://raw.githubusercontent.com/youngqqcn/repo4picgo/master/img/pda.svg)
  • PDA地址是在ed25519曲线之外的,因此,PDA没有对应的私钥。

  • PDA也是确定的, 而不是随机的,因为输入的信息都是预先确定的

  • PDA可以作为链上账户的地址,用来存储状态和获取程序状态

  • PDA账户如果已经被创建,不能重复创建,否则交易失败
  • 深入理解:

如何生成PDA

在 Anchor中生成 PDA

https://solana.com/docs/core/pda#create-pda-accounts

  • Copyrights © 2021-2024 youngqqcn

请我喝杯咖啡吧~