Версії для прикладів у «Транзакції та instructions для розробника» перевірено 2 серпня 2026 року. Стабільна гілка Anchor v1 має релізи 1.0.x і орієнтується на Solana 3.x; Anchor v2 у документації позначений як alpha. Приклади для Anchor 0.29–0.32 залишаються лише відтворюваними прикладами для зафіксованого legacy-середовища: їх не слід переносити в новий проєкт без міграції залежностей і повторного тестування. Клієнт @anchor-lang/core сумісний із legacy @solana/web3.js v1, а не з v2.

Що таке транзакція у Solana

Транзакція у Solana — це серіалізований пакет даних, який містить одну або кілька instructions, список облікових записів, recent_blockhash та криптографічні підписи. Коли транзакція потрапляє до кластера, валідатори перевіряють підписи, сплачують комісію та послідовно виконують кожну instruction. Якщо хоча б одна instruction завершується помилкою — вся транзакція відкочується, а комісія все одно сплачується.

Складові: signers, instructions, recent_blockhash, fee

Транзакція складається з двох частин: Message та масиву signatures. Message містить:

  • account_keys — впорядкований список публічних ключів усіх облікових записів, яких торкаються instructions. Перший ключ завжди належить платнику комісії (fee payer).
  • instructions — масив скомпільованих instructions. Кожна instruction посилається на індекси з account_keys, а не зберігає ключі повторно.
  • recent_blockhash — 32-байтовий хеш останнього блоку. Використовується для запобігання replay-атакам: транзакція з застарілим blockhash буде відхилена. Типовий час життя — близько 150 блоків (приблизно 60–90 секунд на Devnet).

Масив signatures містить електронні підписи (Ed25519), кожен з яких відповідає певному ключу з account_keys. Порядок підписів фіксований: підпис із індексом i належить ключу account_keys[i], якщо цей ключ є signer. Для ключів, що не підписували транзакцію, на відповідній позиції стоїть порожній підпис.

Fee (комісія) розраховується автоматично: базова комісія (5000 lamports) плюс 0 за кожну instruction, що не є викликом програми (write-lock не оплачується окремо). Платником завжди є перший ключ у account_keys.

Ліміт розміру транзакції (1232 байти)

Серіалізована транзакція у Solana не може перевищувати 1232 байти. Це апаратне обмеження, пов'язане з розміром пакету у протоколі Turbine. Ліміт включає всі підписи, усі instructions та метадані. Оскільки кожен підпис Ed25519 займає 64 байти, транзакція з трьома signers вже витрачає 192 байти лише на підписи.

Практичний наслідок: якщо ви передаєте великий обсяг даних у instruction.data, швидко досягаєте ліміту. Для великих даних використовують Account Compression або інші off-chain/наскрізні патерни, але це виходить за межі поточного матеріалу.

Що таке instruction

Instruction — це мінімальна одиниця роботи, яку виконує програма на Solana. Транзакція містить одну або кілька instructions, і кожна з них незалежно адресує конкретну програму.

program_id, accounts, data

Кожна instruction складається з трьох полів:

  • program_id — публічний ключ програми, яка має виконати цю instruction. Це індекс у загальному масиві account_keys транзакції.
  • accounts — масив метаданих облікових записів, які потрібні програмі для виконання. Кожен елемент містить: індекс ключа в account_keys, прапорець is_signer та прапорець is_writable.
  • data — довільний байтовий масив, який програма інтерпретує на власний розсуд. Зазвичай тут міститься ідентифікатор команди (перші 8 байт — дайджест Anchor-селектора або вручну заданий дискримінатор) та серіалізовані параметри.

Як instruction викликає програму

Коли runtime обробляє instruction, він:

  1. Знаходить програму за program_id у списку account_keys.
  2. Перевіряє, чи програма активна (не призупинена).
  3. Завантажує байткод програми (BPF або інший підтримуваний формат).
  4. Передає програмі десеріалізовані accounts та data через entrypoint (зазвичай process_instruction у Rust або обробник у Anchor).
  5. Програма повертає код результату: 0 — успіх, будь-яке інше значення — помилка з відповідним кодом.

Важливо: instruction не «знає» про інші instructions у транзакції. Вона отримує лише свої accounts та data. Взаємодія між instructions можлива через спільні облікові записи, але кожна instruction виконується ізольовано в межах свого виклику.

Порядок виконання instructions

Атомарність: всі або жодна

Instructions у межах однієї транзакції виконуються послідовно у тому порядку, в якому вони записані в масиві. Runtime не паралелить їх. Якщо instruction i змінює стан облікового запису, instruction i+1 побачить оновлений стан.

Однак якщо будь-яка instruction повертає помилку, усі зміни від попередніх instructions відкочуються. Транзакція є атомарною: або всі instructions виконуються успішно, або жодна не залишає сліду в стані (крім сплаченої комісії).

Приклад транзакції з кількома instructions

Типовий приклад — переказ SOL двом різним отримувачам в одній транзакції:

  • Instruction 0: переказ 0.01 SOL з акаунта A на акаунт B (System Program).
  • Instruction 1: переказ 0.02 SOL з акаунта A на акаунт C (System Program).

Обидві instructions звертаються до однієї програми (System Program), але з різними параметрами. Якщо на момент виконання Instruction 1 на аккаунті A недостатньо балансу — вся транзакція відхиляється, і Instruction 0 також відкочується. Жоден отримувач не отримує кошти.

Інший приклад — створення облікового запису та ініціалізація даних у ньому:

  • Instruction 0: System Program створює акаунт із виділеним простором (create_account).
  • Instruction 1: ваша програма записує початкові дані в щойно створений акаунт.

Порядок критичний: якщо змінити instructions місцями, Instruction 1 спробує записати в неіснуючий акаунт і завершиться помилкою.

Signers та підписи

Хто таке signer і чому це важливо

Signer — це обліковий запис, чий приватний ключ використано для створення щонайменше одного підпису в транзакції. Підпис підтверджує, що власник ключа санкціонував цю транзакцію. Runtime перевіряє кожен підпис криптографічно: якщо підпис недійсний — транзакція відхиляється на етапі валідації, ще до виконання будь-якої instruction.

Програма може перевірити, чи є певний акаунт signer, через поле is_signer у метаданих accounts. Це дозволяє реалізовувати логіку авторизації: наприклад, лише власник акаунта може змінити його дані.

Детальний розбір ролі signers, типів підписів та патернів делегування розглядається в окремому матеріалі, тут ми фокусуємося на механіці підписів у контексті транзакції.

Різниця між signer та writable account

Це дві різні властивості облікового запису в контексті instruction, і вони не залежать одна від одної:

Властивість Що означає Хто встановлює
is_signer Обліковий запис підписав транзакцію Визначається наявністю валідного підпису в масиві signatures
is_writable Програма може змінювати дані або баланс цього облікового запису Встановлюється клієнтом при формуванні instruction

Обліковий запис може бути:

  • signer + writable — типово для платника та ініціатора дій.
  • signer + readonly — наприклад, авторизований підписант, який лише підтверджує дію, але його дані не змінюються.
  • not signer + writable — цільовий акаунт, який оновлюється програмою (наприклад, PDA-акаунт із даними).
  • not signer + readonly — акаунт, що читається програмою (наприклад, конфігураційний акаунт або програма-оракул).

Помилкове встановлення is_writable для readonly-акаунта не призведе до помилки виконання, але збільшить комісію транзакції та може спричинити конфлікти блокування, якщо інша транзакція одночасно записує в цей акаунт. Навпаки, якщо програма спробує записати в акаунт, позначений як readonly, runtime завершить instruction з помилкою.

Практичний приклад

Нижче наведено перевірювані кроки на Devnet. Перед виконанням переконайтеся, що у вас встановлено Solana CLI 1.18.x або новішої версії та налаштовано підключення до Devnet.

Створення транзакції через CLI

Крок 1. Перевірте підключення та баланс:

solana config get

Очікуваний результат: RPC URL вказує на https://api.devnet.solana.com.

solana balance

Якщо баланс нульовий, отримайте тестові SOL:

solana airdrop 2

Крок 2. Створіть новий ключ для отримувача:

solana-keygen new --no-bip39-passphrase -o receiver.json

Крок 3. Виконайте переказ із виводом детальної інформації:

solana transfer receiver.json 0.1 --verbose --use-rpc

Параметр --verbose виведе структуру транзакції, включно з signature, recent_blockhash та списком account_keys. Зверніть увагу на поле signatures — там буде один підпис (від вашого ключа), та на account_keys — два ключі (ваш і отримувача).

Розбір структури через solana confirm

Після виконання переказу скопіюйте signature з виходу попередньої команди та виконайте:

solana confirm -v <SIGNATURE>

Очікуваний результат — детальне підтвердження транзакції, що включає:

  • slot — номер слоту, в якому транзакція була включена в блок.
  • confirmation status — finalized, confirmed або processed.
  • transaction — десеріалізована структура з account_keys, instructions та signatures.
  • meta — інформація про комісію (fee), помилки (якщо є) та логи виконання (logMessages).

У блоці transaction.message.instructions ви побачите одну instruction з programId, що відповідає System Program (11111111111111111111111111111111), та масивом accountIndexes — зазвичай [0, 1], де 0 — ваш акаунт (signer + writable), 1 — акаунт отримувача (writable). Поле data містить серіалізовану команду System Program для переказу.

У блоці meta.logMessages будуть логи виконання програми. Для успішного переказу ви побачите записи типу Program 11111111111111111111111111111111 invoke [1] та Program 11111111111111111111111111111111 success.

Цей приклад демонструє базову транзакцію з однією instruction. Додавання другої instruction (наприклад, ще одного переказу) через CLI можна здійснити за допомогою solana transfer з прапорцем --with-compute-unit-price або через програмне формування транзакції з використанням @solana/web3.js, де ви явно конструюєте масив instructions перед підписанням.

Наступний логічний крок у вивченні моделі виконання — детальне знайомство з роллю signers, типами авторизації та делегуванням підписів, що розкривається в наступному матеріалі розділу.

Джерела