Версії для прикладів у «Як написати першу instruction на Anchor» перевірено 2 серпня 2026 року. Стабільна гілка Anchor v1 має релізи 1.0.x і орієнтується на Solana 3.x; Anchor v2 у документації позначений як alpha. Приклади для Anchor 0.29–0.32 залишаються лише відтворюваними прикладами для зафіксованого legacy-середовища: їх не слід переносити в новий проєкт без міграції залежностей і повторного тестування.

Instruction у Anchor — це функція-обробник, яка отримує контекст з акаунтами та вхідні дані, виконує логіку та повертає результат. Нижче — покрокова інструкція для створення першої робочої instruction на Devnet із перевіреним прикладом коду.

Середовище: macOS або Linux (WSL2 для Windows), Anchor 0.30.1, Solana CLI 1.18.26, Rust 1.77.0, кластер — Devnet.

Передумови: ініційований Anchor-проєкт (anchor init), підключений до Devnet (solana config set --url devnet), наявність SOL на Devnet-акаунті для розгортання.

Очікуваний результат: скомпільований і задеплоєний програмний модуль із однією instruction, яку можна викликати через Anchor CLI або клієнтський скрипт.

Структура instruction

Кожна instruction у Anchor складається з двох частин: struct-контексту, що описує акаунти, та функції-обробника, що містить логіку. Вони розміщуються в окремому модулі всередині папки src/ проєкту.

Функція-обробник у модулі instructions

Створіть файл src/instructions/mod.rs та підмодуль для вашої instruction, наприклад src/instructions/set_value.rs. Функція-обробник завжди приймає параметр типу Context<YourStruct> як перший аргумент і повертає Result<()>.

Базовий каркас функції:

pub fn set_value(ctx: Context<SetValue>, new_value: u64) -> Result<()> {
  // логіка тут
  Ok(())
}

Тут SetValue — це назва struct-контексту, а new_value — вхідний параметр, який клієнт передає під час виклику instruction.

Параметри: Context, вхідні дані

Context<T> — це обгортка Anchor, яка містить два поля: accounts (ваш struct з акаунтами) та remaining_accounts (додаткові акаунти, що не описані в struct). У більшості перших instruction достатньо лише accounts.

Вхідні дані — це прості типи Rust або серіалізовані структури (з атрибутом #[derive(AnchorSerialize, AnchorDeserialize)]). Anchor автоматично десеріалізує їх із байтів транзакції.

Визначення акаунтів у Context

Context-struct описує, які акаунти очікує instruction, хто їх підписує та які обмеження на них накладаються. Це центральний механізм безпеки Anchor.

Створення struct з #[derive(Accounts)]

Кожен field у struct має бути анотований атрибутом, що вказує Anchor, як валідувати цей акаунт. Найпоширеніші атрибути: #[account(mut)] — акаунт може змінюватися, #[account(init)] — створення нового акаунта, #[account(signer)] — акаунт має підписати транзакцію.

Приклад для instruction, що записує значення в існуючий акаунт даних:

use anchor_lang::prelude::*;

#[derive(Accounts)]
pub struct SetValue<'info> {
  #[account(mut)]
  pub data_account: Account<'info, DataAccount>,
  pub authority: Signer<'info>,
}

#[account]
pub struct DataAccount {
  pub value: u64,
}

Зверніть увагу: DataAccount тут визначена мінімально для демонстрації instruction. Повне обговорення зв'язування даних та життєвого циклу акаунтів наведено в наступному матеріалі розділу.

Додавання constraints для кожного акаунта

Обмеження (constraints) додаються через параметр constraint всередині атрибута акаунта. Вони виконуються до того, як логіка instruction почне працювати.

#[account(
  mut,
  constraint = data_account.authority == authority.key()
)]
pub data_account: Account<'info, DataAccount>,

Тут constraint гарантує, що лише власник акаунта може змінити значення. Якщо умова не виконується, Anchor повертає помилку ConstraintViolation ще до входу в тіло функції.

Для цього прикладу додайте поле authority до DataAccount:

#[account]
pub struct DataAccount {
  pub authority: Pubkey,
  pub value: u64,
}

Логіка instruction

Всередині функції-обробника ви працюєте з десеріалізованими даними акаунтів безпосередньо через поля Context-struct.

Читання та запис даних акаунтів

Оскільки data_account позначений як mut, ви можете змінювати його поля. Anchor автоматично серіалізує зміни назад у байти після успішного завершення функції.

pub fn set_value(ctx: Context<SetValue>, new_value: u64) -> Result<()> {
  ctx.accounts.data_account.value = new_value;
  Ok(())
}

Читання працює аналогічно — звернення до поля через крапку повертає десеріалізоване значення.

Обробка помилок через Result

Функція повертає Result<()>. Використовуйте оператор ? для поширення помилок або повертайте err! макросом із власним кодом помилки.

pub fn set_value(ctx: Context<SetValue>, new_value: u64) -> Result<()> {
  if new_value > 1_000_000 {
    return err!(MyError::ValueTooHigh);
  }
  ctx.accounts.data_account.value = new_value;
  Ok(())
}

#[error_code]
pub enum MyError {
  #[msg("Значення перевищує допустимий ліміт")]
  ValueTooHigh,
}

Макрос #[error_code] генерує реалізацію AnchorError для вашого enum, тож клієнт отримає зрозуміле повідомлення.

Реєстрація instruction

Щоб Anchor згенерував код для виклику вашої instruction та включив її до IDL, функцію треба додати до макросу #[program] у src/lib.rs.

Додавання до #[program] макросу

use anchor_lang::prelude::*;

mod instructions;

declare_id!("YOUR_PROGRAM_ID_HERE");

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

  pub fn set_value(ctx: Context<instructions::SetValue>, new_value: u64) -> Result<()> {
    instructions::set_value::set_value(ctx, new_value)
  }
}

Функція в #[program] є точкою входу. Вона делегує виклик до реалізації в модулі instructions. Цей патерн дозволяє тримати логіку відокремленою від реєстрації.

Порядок має значення

Порядок функцій всередині #[program] визначає порядок instruction у згенерованому IDL. Клієнтські бібліотеки (наприклад, Anchor-ts) використовують цей порядок для індексації. Якщо ви зміните порядок після генерації клієнтського коду, виклики будуть звертатися до неправильних instruction.

Не забудьте також додати публічний модуль у src/instructions/mod.rs:

pub mod set_value;

Типові помилки

Невірний порядок акаунтів у транзакції

Anchor очікує акаунти в транзакції точно в тому порядку, в якому вони оголошені в Context-struct. Якщо struct оголошує спочатку data_account, а потім authority, то клієнт має передати акаунти саме в такій послідовності. Інакше runtime Solana спробує десеріалізувати неправильний акаунт і транзакція впаде з помилкою типу AccountDidNotDeserialize або InvalidAccountData.

Перевірка: порівняйте порядок полів у struct із порядком у виклику anchor test або у вашому клієнтському скрипті.

Missing account у Context

Якщо клієнт передає менше акаунтів, ніж описано в Context-struct, Anchor поверне помилку InstructionFallbackNotFound або NotEnoughAccountKeys. Це означає, що під час десеріалізації не вистачило акаунтів для заповнення всіх полів struct.

Поширена причина — додавання нового поля до Context-struct без оновлення клієнтського коду, який формує транзакцію.

Щоб перевірити instruction локально без розгортання на Devnet, виконайте:

anchor build
anchor test --skip-build

Якщо тести проходять, instruction коректно визначена, акаунти валідуються, і логіка виконується. Після цього можна деплоїти на Devnet командою anchor deploy та викликати instruction через anchor run з тестовим скриптом.

Наступний крок: після створення першої instruction варто зрозуміти, як акаунти визначаються, ініціалізуються та зв'язуються з даними — це розкрито в наступному матеріалі розділу.

Джерела