Версії для прикладів у «Rust і Anchor для початківця» перевірено 2 серпня 2026 року. Стабільна гілка Anchor v1 має релізи 1.0.x і орієнтується на Solana 3.x; Anchor v2 у документації позначений як alpha. Приклади для Anchor 0.29–0.32 залишаються лише відтворюваними прикладами для зафіксованого legacy-середовища: їх не слід переносити в новий проєкт без міграції залежностей і повторного тестування.
Базовий Rust для Solana-розробника
Чому Rust для Solana
Solana-програми компілюються у байт-код для віртуальної машини BPF (Berkeley Packet Filter). Rust — основна мова для цього середовища: він дає контроль над пам'яттю без сміттєзбирача, має систему власностей, яка на етапі компіляції запобігає raced-умовам, і генерує щільний бінарний код, що критично для обмеженого бюджету обчислень Solana. Альтернативи (C, C++) існують, але екосистема інструментів Solana орієнтована насамперед на Rust.
Основи синтаксису Rust
Змінні в Rust за замовчуванням незмінні. Щоб змінити значення, потрібне ключове слово mut:
let x: u64 = 10;
let mut y: u64 = 20;
y += 1;
Функції визначаються через fn, тип повернення вказується після ->. У Solana-програмах майже всі публічні функції повертають Result<()> — успіх або помилку:
fn add(a: u64, b: u64) -> Result<u64> {
Ok(a + b)
}
Обробка помилок через ? розгортає Err і повертає його з функції, що робить ланцюжки викликів читабельними.
Власності та посилання
Кожне значення в Rust має єдиного власника. Коли власник виходить із області видимості, пам'ять звільняється автоматично. Посилання (&T) дозволяють читати дані без передачі власності. Змінне посилання (&mut T) дозволяє модифікувати дані, але в кожен момент існує лише одне змінне посилання на це значення.
У контексті Solana це означає: коли ви отримуєте акаунт у контексті instruction, ви працюєте з посиланням на дані, які належать блокчейну, а не копіюєте їх.
Структури та enum-и
Структури — основний спосіб опису стану акаунта в Solana:
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct Counter {
pub count: u64,
}
Enum-и в Anchor використовуються для визначення помилок програми:
#[error_code]
pub enum MyError {
#[msg("Лічильник перевищив максимум")]
Overflow,
}
Крос-компіляція для BPF
Solana-програми не компілюються для вашої операційної системи. Потрібен інструментарій solana-tools, який додає ціль sbf-solana-solana до Rust-компілятора. Перевірте наявність цієї цілі командою rustup target list | grep sbf. Якщо ціль відсутня, встановіть її через rustup target add sbf-solana-solana. Конкретну версію інструментарію перевірте в офіційній документації Solana, оскільки вона змінюється між релізами.
Що таке Anchor та чому він потрібен
Проблеми написання програм без Anchor
Без Anchor Solana-програма пишеться на «голому» Rust із прямими викликами до solana-program. Розробник самостійно: десеріалізує дані акаунтів, перевіряє права власності, обчислює адреси PDA (Program Derived Address), керує розміром простору для акаунтів, формує відповіді про помилки. Кожна з цих операцій містить місця для помилок, які важко діагностувати. Типова програма без Anchor на 100 рядків логіки потребує 300–400 рядків сервісного коду.
Що дає Anchor
Anchor — це фреймворк, який автоматизує рутину:
- Десеріалізація та серіалізація — дані акаунтів перетворюються на Rust-структури автоматично за допомогою макросів.
- Перевірка акаунтів — через атрибути (constraints) ви декларативно вказуєте, хто має право підписувати, якому програмі належить акаунт, чи є акаунт PDA.
- Генерація IDL — автоматично створюється опис інтерфейсу програми, який клієнтські SDK використовують для типобезпечних викликів.
- Тестування — інтеграційні тести на TypeScript працюють з програмою через згенерований IDL без ручного конструювання транзакцій.
Архітектура Anchor
Anchor розділяє програму на три шари:
- Accounts — опис, які акаунти потрібні для instruction і які обмеження на них накладаються.
- Instructions — чиста логіка, що працює з вже перевіреними акаунтами.
- Errors — централізований опис помилок програми.
Цей поділ змушує розробника чітко відокремлювати перевірку від виконання, що робить код безпечнішим і читабельнішим.
Anchor у порівнянні з альтернативами
Головна альтернатива — написання програм на «голому» Rust. Це дає максимальний контроль, але значно збільшує час розробки та кількість помилок. Існують також експериментальні фреймворки для інших мов, проте в екосистемі Solana саме Anchor має найбільшу кількість прикладів, бібліотек та готових рішень.
Наступний крок
Перейдемо до створення першого проєкту, щоб побачити Anchor у дії. Після цього детально розберемо кожен компонент.
Перший проєкт на Anchor
Створення проєкту
Передумови: встановлені Rust (stable-канал), Solana CLI та Anchor CLI. Конкретні версії перевірте в офіційній документації, оскільки сумісність між Solana CLI та Anchor CLI жорстко прив'язана до релізів. Створіть новий проєкт:
anchor init counter_app
cd counter_app
Ця команда генерує повну структуру проєкту з шаблонною програмою.
Огляд ключових файлів
Після ініціалізації ви отримуєте каталог programs/counter_app/ з файлом src/lib.rs — це основний файл програми. У корені проєкту знаходиться Anchor.toml — конфігурація проєкту. Каталог tests/ містить файл counter_app.ts для інтеграційних тестів.
Написання першої програми
Замініть вміст programs/counter_app/src/lib.rs на просту програму лічильника:
use anchor_lang::prelude::*;
declare_id!("CtrErXUV4q3ZDj4r7Xp6kY8vL2nN5mQ9wE0tR1bY3cF5");
#[program]
pub mod counter_app {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count = 0;
Ok(())
}
pub fn increment(ctx: Context<Increment>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count += 1;
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = user, space = 8 + 8)]
pub counter: Account<'info, Counter>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut)]
pub counter: Account<'info, Counter>,
}
#[account]
pub struct Counter {
pub count: u64,
}
Зверніть увагу: ID у declare_id! — це плейсхолдер. Після першої збірки Anchor згенерує реальний ключова пара і оновить цей ID автоматично.
Збірка та перевірка
anchor build
Очікуваний результат: у каталозі target/types/ з'являється файл counter_app.json (IDL), а у target/deploy/ — скомпільований counter_app.so. Якщо збірка завершилась без помилок, програма синтаксично коректна.
Типові помилки на старті
- «sbf-solana-solana target not found» — не встановлено ціль крос-компіляції. Встановіть через rustup target add з версією, що відповідає вашій версії Solana CLI.
- Невідповідність версій Anchor та Solana — перевірте матрицю сумісності в репозиторії Anchor.
- Помилки макросів — найчастіше пов'язані з неправильним типом акаунта або відсутнім обов'язковим полем у структурі.
Структура Anchor-проєкту: каталоги та файли
Каталог programs/
Містить вихідний код усіх програм проєкту. Кожна програма — окремий Cargo-крейт зі своїм Cargo.toml. У простому проєкті тут одна програма. У складних проєктах може бути кілька програм, які взаємодіють через CPI (Cross-Program Invocation).
Каталог tests/
Інтеграційні тести на TypeScript. Вони запускають локальний валідатор, деплоять програму і викликають інструкції через згенерований IDL. Це не модульні тести Rust — вони перевіряють поведінку програми на рівні транзакцій.
Каталог migrations/
Скрипти розгортання для різних середовищ. У базовому шаблоні містить deploy.sh, який викликає anchor deploy. У реальних проєктах тут можуть бути скрипти міграції стану.
Файл Anchor.toml
Головний конфігураційний файл проєкту. Ключові секції:
- [features] — прапорці компіляції (seeds, skip-lint).
- [programs.localnet] — відображення імен програм на їхні ID для локального тестування.
- [provider] — кластер і гаманець за замовчуванням для CLI-команд.
- [scripts] — команда запуску тестів.
Файл Cargo.toml програми
Стандартний манифест Rust-крейта. Ключова залежність — anchor-lang. Версія anchor-lang має збігатися з версією Anchor CLI. Наприклад, Anchor CLI 0.29.x працює з anchor-lang 0.29.x.
Anchor constraints: обмеження та перевірки в програмах
Що таке constraints
Constraints — це атрибути, які ви розміщуєте над полями структур Accounts. Вони виконуються до входу в логіку instruction. Якщо хоча б один constraint не виконується, транзакція відхиляється з описовою помилкою. Це замінює десятки рядків ручних перевірок.
Основні constraints
- #[account(mut)] — акаунт може бути змінений під час виконання instruction. Без цього атрибута Anchor вважає акаунт лише для читання.
- #[account(init, payer = ..., space = ...)] — створює новий акаунт, виділяє простір і оплачує його з вказаного акаунта.
- #[account(seeds = [...], bump)] — перевіряє, що акаунт є PDA з вказаними насінинами.
- #[account(has_one = authority)] — перевіряє, що поле authority акаунта збігається з вказаним акаунтом.
Constraints для безпеки
#[account(constraint = ...)] — довільна логічна умова. Використовуйте для перевірок, які не покриваються вбудованими атрибутами:
#[account(
mut,
constraint = counter.authority == user.key()
)]
pub counter: Account<'info, Counter>,
Це гарантує, що лише авторизований користувач може змінити лічильник. Концептуальний приклад — у production-рішеннях для складних перевірок краще винести логіку в окрему функцію та повертати Result<()>.
Constraints для PDA
Для PDA-акаунтів обов'язкові seeds та bump. Anchor автоматично обчислює bump і перевіряє адресу:
#[account(
init,
payer = user,
space = 8 + 8,
seeds = [b"counter", user.key().as_ref()],
bump
)]
pub counter: Account<'info, Counter>,
Тут PDA похідний від рядка "counter" і публічного ключа користувача. Це гарантує унікальність акаунта на кожного користувача.
Типові помилки
- Відсутній mut для акаунта, який змінюється — помилка виконання з повідомленням про незмінний акаунт.
- Неправильний space — якщо виділити замало місця, запис даних завершиться помилкою. Формула: 8 байт (дискримінатор Anchor) + сума розмірів усіх полів.
- Невірні seeds — PDA не збігається з переданим акаунтом. Перевірте порядок і вміст насінин.
Що таке IDL у Anchor
Означення IDL
IDL (Interface Definition Language) — це JSON-файл, який описує публічний інтерфейс вашої програми: назви інструкцій, їхні аргументи, структури акаунтів та типи полів. Аналогія — Swagger/OpenAPI для REST API.
Як генерується IDL
IDL генерується автоматично під час anchor build. Файл з'являється у target/types/ім'я_програми.json. Також копія IDL вбудовується у бінарний файл програми (.so) і може бути витягнута з деплойованої програми командою anchor idl fetch.
Як використовується IDL
Клієнтський код на TypeScript імпортує IDL і отримує типобезпечний доступ до програми:
import { Program, AnchorProvider } from "@coral-xyz/anchor";
import { CounterApp } from "../target/types/counter_app";
const program = new Program<CounterApp>(
IDL,
PROGRAM_ID,
provider
);
Без IDL клієнту довелося б вручну конструювати транзакції з правильним порядком акаунтів і серіалізацією даних.
IDL та версіонування
Кожна зміна інтерфейсу програми (додавання інструкцій, зміна полів акаунтів) генерує новий IDL. У production-середовищі версіонування IDL критичне для сумісності з клієнтами. Зберігайте IDL у системі контролю версій разом із кодом програми.
Типові помилки
- Розбіжність IDL та реальної програми — виникає, коли клієнт використовує старий IDL з оновленою програмою. Рішення — перезбірка та оновлення IDL на клієнті.
- Відсутній IDL — якщо ви деплоїте програму без IDL, клієнти не зможуть автоматично з ним працювати.
Як написати першу instruction на Anchor
Структура instruction
Кожна instruction у Anchor — це публічна функція всередині блоку #[program]. Вона приймає єдиний аргумент типу Context<T>, де T — структура, що описує потрібні акаунти. Додаткові аргументи — це дані, які серіалізуються з інструкції транзакції.
Визначення акаунтів у Context
Структура Context реалізує трейт Accounts. Кожне поле цієї структури — це акаунт з відповідними constraints:
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut)]
pub counter: Account<'info, Counter>,
}
Anchor автоматично десеріалізує акаунт counter у структуру Counter і перевіряє, що програма є власником цього акаунта.
Логіка instruction
Всередині функції ви працюєте з ctx.accounts — це вже перевірені та десеріалізовані акаунти:
pub fn increment(ctx: Context<Increment>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count += 1;
Ok(())
}
Зміни автоматично серіалізуються назад у акаунт після завершення instruction.
Реєстрація instruction
Достатньо додати публічну функцію в блок #[program]. Anchor автоматично реєструє її в IDL і створює диспетчер, який маршрутизує виклики за індексом інструкції. Немає окремого кроку реєстрації.
Типові помилки
- Невідповідність порядку акаунтів — клієнт має передавати акаунти в тому ж порядку, в якому вони оголошені в структурі Context.
- Забутий Ok(()) — функція повинна повертати Result<()>. Якщо логіка завершується успішно, обов'язково поверніть Ok(()).
- Доступ до неініціалізованого акаунта — якщо акаунт не пройшов init, спроба десеріалізації завершиться помилкою.
Anchor accounts: визначення та зв'язування даних
Що таке account struct
Account struct — це Rust-структура з атрибутом #[account], яка описує схему даних, що зберігаються на блокчейні:
#[account]
pub struct Counter {
pub count: u64,
}
Атрибут #[account] автоматично реалізує трейти AccountSerialize, AccountDeserialize та AnchorSerialize/AnchorDeserialize. Також він додає 8-байтовий дискримінатор на початку серіалізованих даних — хеш від імені структури, який дозволяє Anchor визначати тип акаунта.
Визначення стану акаунта
Використовуйте типи з фіксованим розміром: u64, i64, bool, Pubkey (32 байти). Для рядків використовуйте String — Anchor серіалізує їх із префіксом довжини. Для масивів фіксованої довжини — [u8; 32]. Уникайте типів із динамічним розміром (Vec) без явної потреби, оскільки це ускладнює обчислення space.
Ініціалізація акаунта
Акаунт ініціалізується через constraint init у структурі Context:
#[account(init, payer = user, space = 8 + 8)]
pub counter: Account<'info, Counter>,
Тут 8 + 8 = 8 байт дискримінатора + 8 байт для поля count: u64. Платник user сплачує rent за виділений простір. Після ініціалізації акаунт належить поточній програмі.
Зв'язування акаунтів
Акаунти можуть посилатися один на одного через поля типу Pubkey. Наприклад, акаунт конфігурації може зберігати pubkey акаунта-власника. Для перевірки цих зв'язків використовуйте has_one або constraint.
Типові помилки
- Невірний space — найпоширеніша помилка. Завжди рахуйте: 8 (дискримінатор) + сума розмірів полів. Для Pubkey — 32 байти, для String — 4 (довжина) + максимальна довжина рядка.
- Забутий дискримінатор — якщо ви вручну читаєте акаунт поза Anchor, пам'ятайте про перші 8 байт.
- Зміна схеми без міграції — додавання поля до існуючого акаунта без повторної ініціалізації призведе до помилки десеріалізації.
Як тестувати просту програму локально
Налаштування тестового середовища
Передумови: встановлені Node.js (перевірте сумісну версію в документації Anchor), yarn або npm, та залежності проєкту (yarn install у корені проєкту). Anchor автоматично запускає локальний валідатор перед тестами.
Написання інтеграційного тесту
Замініть вміст tests/counter_app.ts:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { CounterApp } from "../target/types/counter_app";
import { assert } from "chai";
describe("counter_app", () => {
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const program = anchor.workspace.CounterApp as Program<CounterApp>;
it("Ініціалізує лічильник зі значенням 0", async () => {
const counter = anchor.web3.Keypair.generate();
await program.methods
.initialize()
.accounts({ counter: counter.publicKey })
.signers([counter])
.rpc();
const account = await program.account.counter.fetch(counter.publicKey);
assert.strictEqual(account.count, 0);
});
it("Збільшує лічильник на 1", async () => {
const counter = anchor.web3.Keypair.generate();
await program.methods
.initialize()
.accounts({ counter: counter.publicKey })
.signers([counter])
.rpc();
await program.methods
.increment()
.accounts({ counter: counter.publicKey })
.rpc();
const account = await program.account.counter.fetch(counter.publicKey);
assert.strictEqual(account.count, 1);
});
});
Виклик instruction у тесті
Метод program.methods.ім'я_інструкції() повертає будівельник, де ви вказуєте акаунти через .accounts(), підписанти через .signers() і відправляєте транзакцію через .rpc(). Порядок акаунтів у .accounts() не має значення — Anchor зіставляє їх за іменами полів структури Context.
Перевірка результатів
program.account.counter.fetch(address) читає стан акаунта з блокчейну та повертає десеріалізовану структуру. Використовуйте assert для перевірки очікуваних значень. Якщо акаунт не існує, fetch викине помилку.
Типові помилки
- «Wallet not found» — гаманець за замовчуванням (~/.config/solana/id.json) не існує. Створіть його через solana-keygen new.
- Локальний валідатор не запускається — перевірте, чи встановлено solana-test-validator, і чи не зайнятий порт 8899.
- Таймаут тесту — збільште таймаут у Anchor.toml у секції [scripts] або запустіть тест з прапорцем --timeout.
Як деплоїти Anchor-програму на Devnet
Підготовка до розгортання
Передумови: гаманець з SOL на Devnet-акаунті. Отримати тестові токени можна через кран — перевірте актуальну адресу крану в документації Solana, оскільки вона може змінюватися. Перевірте баланс:
solana balance --url devnet
Налаштування Anchor.toml для Devnet
Змініть секцію [provider] у Anchor.toml:
[provider]
cluster = "Devnet"
wallet = "~/.config/solana/id.json"
Також додайте секцію для Devnet-програми, якщо її ще немає:
[programs.devnet]
counter_app = "ВАШ_РЕАЛЬНИЙ_PROGRAM_ID"
Реальний program ID з'являється після першої збірки — його можна знайти у згенерованому IDL-файлі або в файлі target/deploy/counter_app-keypair.json.
розгортання через Anchor CLI
anchor deploy --provider.cluster devnet
Очікуваний результат: програма депloyована на Devnet, її ID виведено в консоль. Після розгортання ви можете перевірити програму:
solana program show ВАШ_PROGRAM_ID --url devnet
Оновлення програми
Якщо ви змінили код і хочете оновити програму на Devnet, виконайте anchor deploy повторно. Anchor використовує той самий keypair з target/deploy/, тому програма оновиться на місці. Увага: на Mainnet оновлення програми має обмеження (buffer та authority), але на Devnet ці обмеження не застосовуються.
Типові помилки
- Insufficient funds — на гаманці недостатньо SOL для розгортання. Розмір розгортання залежить від розміру .so файлу. Отримайте більше тестових SOL через кран.
- «Program ID mismatch» — ID у Anchor.toml не збігається з keypair. Скопіюйте реальний ID з target/types/counter_app.json (поле address).
- Помилка підключення до Devnet — перевірте мережеве з'єднання та спробуйте вказати RPC-ендпоінт явно через --url.
Типові помилки Anchor та їх вирішення
Помилки компіляції
Найчастіші причини: невідповідність версій anchor-lang та Anchor CLI, відсутні трейти для серіалізації, неправильні типи в структурах Context. Читайте повідомлення компілятора зверху вниз — перша помилка часто викликає хибні каскадні повідомлення нижче. Виправте першу помилку і перезбірте.
Помилки виконання (runtime)
Це помилки, які виникають під час обробки транзакції на валідаторі. У логах вони відображаються з кодом та описом. Найпоширеніші:
- InstructionFallbackNotFound — програма не містить інструкції з вказаним індексом. Зазвичай означає розбіжність між IDL клієнта та реальною програмою.
- AccountNotProgramOwned — акаунт, який ви намагаєтеся десеріалізувати, не належить вашій програмі. Перевірте constraints.
- ConstraintMut — ви намагаєтеся змінити акаунт без mut.
Помилки тестування
- Тест «зависає» — найчастіше локальний валідатор не запустився. Перевірте логи виводу.
- «Account does not exist» у fetch — інструкція ініціалізації не виконалась успішно, або ви читаєте за неправильною адресою.
- «Signature verification failed» — ви не додали необхідний signer у .signers().
Помилки розгортання
- «Program too large» — розмір .so файлу перевищує ліміт. Оптимізуйте залежності або розбийте програму на кілька.
- «Deployment failed» без конкретного повідомлення — перевірте підключення до кластера та наявність ключової пари в target/deploy/.
Як діагностувати
Для локальних тестів використовуйте детальні логи:
RUST_LOG=anchor_lang::error=debug anchor test
Для Devnet-помилок перевіряйте логи транзакції через експлорер або через CLI:
solana confirm -v ТРАНЗАКЦІЯ_SIG --url devnet
Звертайте увагу на поле logs у відповіді — там містяться програмні помилки з описом. Якщо повідомлення містить AnchorError з кодом error_code, знайдіть цей код у вашому enum помилок.