Anchor
Anchor для початківців

Anchor для початківців

Розробка на стороні клієнта

Більшість децентралізованих додатків (dApps) використовують TypeScript для взаємодії з розгорнутими програмами Solana. Розуміння того, як інтегрувати вашу програму на стороні клієнта, є важливим для створення функціональних додатків.

Клієнтський SDK Anchor

Anchor спрощує клієнтську взаємодію з програмами Solana через файл мови опису інтерфейсу (IDL), який відображає структуру вашої програми.

У поєднанні з бібліотекою TypeScript від Anchor (@coral-xyz/anchor), IDL забезпечує оптимізований підхід до створення інструкцій та транзакцій.

Налаштування

Пакет @coral-xyz/anchor встановлюється автоматично при створенні програми Anchor. Після запуску anchor build, Anchor генерує:

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

Ці файли абстрагують більшу частину базової складності. Перенесіть їх до вашого TypeScript клієнта, використовуючи таку структуру:

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

Файл integration.ts містить логіку взаємодії з програмою. Файл <program-name>.json — це IDL, а <program-name>.ts містить згенеровані типи TypeScript.

Щоб використовувати адаптер гаманця з TypeScript SDK від Anchor, створіть об'єкт Provider, який поєднує Connection (localhost, devnet або mainnet) та Wallet (адресу, яка оплачує та підписує транзакції).

Налаштуйте Wallet та Connection:

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

Хук useWallet з @solana/wallet-adapter-react несумісний з об'єктом Wallet, який очікує Provider від Anchor. Саме тому ми використовуємо хук useAnchorWallet.

Створіть об'єкт Provider і встановіть його як типовий:

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

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

setProvider(provider);

Програма

Об'єкт Program від Anchor створює спеціальний API для взаємодії з програмами Solana. Цей API служить центральним інтерфейсом для всієї комунікації з програмами в мережі:

  • Надсилання транзакцій,
  • Отримання десеріалізованих акаунтів,
  • Декодування даних інструкцій,
  • Підписка на зміни акаунтів,
  • Прослуховування подій

Створіть об'єкт Program, імпортуючи типи та IDL:

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 Methods Builder для створення інструкцій та транзакцій. MethodsBuilder використовує IDL для надання спрощеного формату побудови транзакцій, які викликають інструкції програми.

Базовий шаблон MethodsBuilder:

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

API використовує іменування camelCase замість конвенції snake_case у Rust. Викликайте інструкції, використовуючи синтаксис з крапкою з назвою інструкції, передаючи аргументи як значення, розділені комами.

Передавайте додаткових підписантів, окрім провайдера, використовуючи .signers().

Акаунти

Використовуйте синтаксис з крапкою для виклику .accounts на MethodsBuilder, передаючи об'єкт з кожним акаунтом, який очікує інструкція на основі 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()),
    },
  },
]);

Це отримує всі акаунти Counter, де перше поле дорівнює 0.

Для перевірки змін даних акаунту, отримайте десеріалізовані дані акаунту для конкретного акаунту за допомогою 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,
]);

Події та вебхуки

Замість того, щоб отримувати дані з блокчейну щоразу, коли користувачі підключають свої гаманці, налаштуйте системи, які прослуховують блокчейн і зберігають відповідні дані в базі даних.

Існує два основні підходи для прослуховування подій у блокчейні:

  • Опитування: клієнт періодично перевіряє наявність нових даних через певні інтервали. Сервер відповідає найновішими даними незалежно від змін, потенційно повертаючи дублікати інформації.
  • Потокова передача: сервер надсилає дані клієнту лише при виникненні оновлень. Це забезпечує ефективнішу передачу даних у реальному часі, оскільки передаються лише відповідні зміни.

Для потокової передачі інструкцій Anchor використовуйте вебхуки, які прослуховують події та надсилають їх на ваш сервер, коли вони відбуваються. Наприклад, оновлюйте запис у базі даних щоразу, коли відбувається продаж NFT на вашому маркетплейсі.

Для застосунків з надзвичайно низькою затримкою, де важлива різниця в 5 мс, вебхуки можуть не забезпечити достатню швидкість.

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: 6d01265