Anchor
Anchor for Dummies

Anchor for Dummies

客户端开发

大多数 dApp 使用 TypeScript 与已部署的 Solana 程序交互。了解如何在客户端集成您的程序是构建功能性应用程序的关键。

Anchor Client SDK

Anchor 通过一个与您的程序结构相匹配的接口描述语言(IDL)文件简化了与 Solana 程序的客户端交互。

结合 Anchor 的 TypeScript 库(@coral-xyz/anchor),IDL 提供了一种简化的方式来构建指令和交易。

设置

@coral-xyz/anchor 包在创建 Anchor 程序时会自动安装。在运行 anchor build 后,Anchor 会生成:

  • 位于 target/idl/<program-name>.json 的 IDL
  • 位于 target/types/<program-name>.ts 的 TypeScript SDK

这些文件抽象了许多底层的复杂性。使用以下结构将它们传输到您的 TypeScript 客户端:

 
src
├── anchor
│     ├── <program-name>.json
│     └── <program-name>.ts
└── integration.ts

integration.ts 文件包含程序交互逻辑。<program-name>.json 文件是 IDL,而 <program-name>.ts 包含生成的 TypeScript 类型。

要将钱包适配器与 Anchor 的 TypeScript SDK 一起使用,请创建一个 Provider 对象,该对象结合了连接(localhost、devnet 或 mainnet)和钱包(支付和签署交易的地址)。

设置钱包和连接:

ts
import { useAnchorWallet, useConnection } from "@solana/wallet-adapter-react";
 
const { connection } = useConnection();
const wallet = useAnchorWallet();

来自 useWallet@solana/wallet-adapter-react hook 与 Anchor 的 Provider 所期望的钱包对象不兼容。这就是我们使用 useAnchorWallet hook 的原因。

创建 Provider 对象并将其设置为默认值:

 
import { AnchorProvider, setProvider } from "@coral-xyz/anchor";

const provider = new AnchorProvider(connection, wallet, {
  commitment: "confirmed",
});

setProvider(provider);

程序

Anchor 的 Program 对象为与 Solana 程序交互创建了一个自定义 API。此 API 是所有链上程序通信的核心接口:

  • 发送交易,
  • 获取反序列化的账户,
  • 解码指令数据,
  • 订阅账户更改,
  • 监听事件

通过导入类型和IDL创建Program对象:

ts
import <program-name> from "./<program-name>.json";
import type { <Program-Type> } from "./<program-name>.ts";
import { Program, Idl } from "@coral-xyz/anchor";
 
const program = new Program(<program-name> as <Program-Type>);

如果您尚未设置默认提供者,请显式指定:

ts
const program = new Program(<program-name> as <Program-Type>, provider);

配置完成后,使用Anchor方法构建器来构建指令和交易。MethodsBuilder 使用IDL提供了一种简化的格式,用于构建调用程序指令的交易。

基本的MethodsBuilder模式:

ts
await program.methods
  .instructionName(instructionDataInputs)
  .accounts({})
  .signers([])
  .rpc();

API使用camelCase命名方式,而不是Rust的snake_case约定。通过点语法调用指令名称,并以逗号分隔的值传递参数。

通过.signers()传递提供者之外的额外签名者。

账户

使用点语法在MethodsBuilder上调用.accounts,并传递一个对象,其中包含指令根据IDL期望的每个账户。

从Anchor 0.30.0开始,可以自动解析的账户(如PDA或显式地址)包含在IDL中,并且在.accounts调用中不再需要(.accountPartial()成为默认值)。要手动传递所有账户,请使用.accountsStrict()

交易

通过Anchor发送交易的默认方法是.rpc(),它将交易直接发送到区块链。

对于需要后端签名的场景(例如在前端使用用户的钱包创建交易,然后使用后端密钥对安全签名),请使用.transaction()

ts
const transaction = await program.methods
  .instructionName(instructionDataInputs)
  .accounts({})
  .transaction();
 
//... Sign the transaction in the backend
 
// Send the transaction to the chain
await sendTransaction(transaction, connection);

要捆绑多个Anchor指令,请使用.instruction()获取指令对象:

ts
// Create first instruction
const instructionOne = await program.methods
  .instructionOneName(instructionOneDataInputs)
  .accounts({})
  .instruction();
 
// Create second instruction
const instructionTwo = await program.methods
  .instructionTwoName(instructionTwoDataInputs)
  .accounts({})
  .instruction();
 
// Add both instructions to one transaction
const transaction = new Transaction().add(instructionOne, instructionTwo);
 
// Send transaction
await sendTransaction(transaction, connection);

Fetch and Filter Accounts

当您的程序创建数百个账户时,跟踪它们会变得具有挑战性。Program对象提供了高效获取和筛选程序账户的方法。

获取特定账户类型的所有地址:

ts
const accounts = await program.account.counter.all();

使用memcmp标志筛选特定账户:

ts
const accounts = await program.account.counter.all([
  {
    memcmp: {
      offset: 8,
      bytes: bs58.encode(new BN(0, "le").toArray()),
    },
  },
]);

这将获取所有第一个字段等于 0 的 Counter 账户。

要检查账户数据是否发生变化,可以使用 fetch 获取特定账户的反序列化账户数据:

ts
const account = await program.account.counter.fetch(ACCOUNT_ADDRESS);

同时获取多个账户:

ts
const accounts = await program.account.counter.fetchMultiple([
  ACCOUNT_ADDRESS_ONE,
  ACCOUNT_ADDRESS_TWO,
]);

事件和 Webhooks

与其每次用户连接钱包时都获取链上数据,不如设置监听区块链的系统,并将相关数据存储到数据库中。

监听链上事件主要有两种方法:

  • 轮询:客户端以一定的时间间隔反复检查新数据。服务器无论数据是否发生变化都会返回最新数据,可能会返回重复信息。
  • 流式传输:服务器仅在更新发生时将数据推送到客户端。这种方式更高效,能够实时传输数据,因为只传输相关的变化。

对于流式传输 Anchor 指令,可以使用监听事件的 webhooks,并在事件发生时将其发送到您的服务器。例如,每当您的市场上发生 NFT 销售时,更新数据库条目。

对于极低延迟的应用程序(如 5 毫秒的差异也很重要),webhooks 可能无法提供足够的速度。

Anchor 提供了两个用于发出事件的宏:

  • emit!():通过 sol_log_data() 系统调用直接将事件发出到程序日志中,事件数据以 base64 字符串编码,并以 "Program Data" 为前缀。
  • emit_cpi!():通过跨程序调用(CPI)发出事件。事件数据被编码并包含在 CPI 的指令数据中,而不是程序日志中。

emit!()

程序实现:

rust
use anchor_lang::prelude::*;
 
declare_id!("8T7MsCZyzxboviPJg5Rc7d8iqEcDReYR2pkQKrmbg7dy");
 
#[program]
pub mod event {
    use super::*;
 
    pub fn emit_event(_ctx: Context<EmitEvent>, input: String) -> Result<()> {
        emit!(CustomEvent { message: input });
        Ok(())
    }
}
 
#[derive(Accounts)]
pub struct EmitEvent {}
 
#[event]
pub struct CustomEvent {
    pub message: String,
}

使用 Anchor SDK 辅助工具进行 base64 解码的客户端事件监听:

ts
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { Event } from "../target/types/event";
 
describe("event", () => {
  // Configure the client to use the local cluster.
  anchor.setProvider(anchor.AnchorProvider.env());
 
  const program = anchor.workspace.Event as Program<Event>;
 
  it("Emits custom event", async () => {
    // Set up listener before sending transaction
    const listenerId = program.addEventListener("customEvent", event => {
      // Process the event data
      console.log("Event Data:", event);
    });
  });
});

emit_cpi!()

程序实现:

rust
use anchor_lang::prelude::*;
 
declare_id!("2cDQ2LxKwQ8fnFUz4LLrZ157QzBnhPNeQrTSmWcpVin1");
 
#[program]
pub mod event_cpi {
    use super::*;
 
    pub fn emit_event(ctx: Context<EmitEvent>, input: String) -> Result<()> {
        emit_cpi!(CustomEvent { message: input });
        Ok(())
    }
}
 
#[event_cpi]
#[derive(Accounts)]
pub struct EmitEvent {}
 
#[event]
pub struct CustomEvent {
    pub message: String,
}

客户端事件解码:

ts
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { EventCpi } from "../target/types/event_cpi";
 
describe("event-cpi", () => {
  // Configure the client to use the local cluster.
  anchor.setProvider(anchor.AnchorProvider.env());
  const program = anchor.workspace.EventCpi as Program<EventCpi>;
 
  it("Emits custom event", async () => { 
    // Fetch the transaction data
    const transactionData = await program.provider.connection.getTransaction(
      transactionSignature,
      { commitment: "confirmed" },
    );
 
    // Decode the event data from the CPI instruction data
    const eventIx = transactionData.meta.innerInstructions[0].instructions[0];
    const rawData = anchor.utils.bytes.bs58.decode(eventIx.data);
    const base64Data = anchor.utils.bytes.base64.encode(rawData.subarray(8));
    const event = program.coder.events.decode(base64Data);
 
    console.log(event);
  });
});
Blueshift © 2025Commit: 0ce3b0d
Blueshift | Anchor for Dummies | Client Side Development