Українська спільнота Solana: нові матеріали, безпека та подіїСпільнота Solana в TelegramПриєднатися →
Основи розробки на Solana

Токени, PDA та CPI

Токени, PDA (Program Derived Address) та CPI (Cross-Program Invocation) — три механізми, які перетворюють Solana з простої машини транзакцій на платформу для складних децентралізованих застосунків. Токени зберігають цінність, PDA дає програмам власність над…

0 підрозділів0 матеріалів на цьому рівніОновлено 1 серпня 2026

Токени, PDA (Program Derived Address) та CPI (Cross-Program Invocation) — три механізми, які перетворюють Solana з простої машини транзакцій на платформу для складних децентралізованих застосунків. Токени зберігають цінність, PDA дає програмам власність над даними, а CPI дозволяє програмам взаємодіяти між собою. Без розуміння цих трьох шарів неможливо написати жоден робочий смарт-контракт на Solana.

Версії для прикладів у «Токени, PDA та CPI» перевірено 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.

Усі приклади нижче використовують кластер Devnet, Solana CLI 1.18.x, Anchor 0.30.x та @solana/spl-token 0.3.x. Перед виконанням переконайтеся, що ви налаштували Devnet:

Середовище: Solana CLI 1.18.x, Anchor 0.30.x, Node.js 20.x, кластер Devnet
Передумова: встановлені solana-cli, anchor, @solana/web3.js, @solana/spl-token
Перевірка кластера: solana config get має показувати rpc url: https://api.devnet.solana.com

Що таке SPL Token та як він працює

Огляд SPL Token Program

SPL Token Program — це окрема програма на Solana, яка реалізує стандарт токенів. Вона не вбудована в runtime, а розгортається як звичайна програма з адресою TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA. Будь-яка ваша програма може взаємодіяти з нею через CPI.

SPL Token Program працює з двома основними об'єктами: mint (емісійний акаунт, що визначає тип токена) та token account (акаунт конкретного власника, що зберігає баланс).

Типи токен-акаунтів

  • Mint-акаунт — описує токен: загальна пропозиція, кількість знаків після коми, хто має право mint-нути та freeze-нути. Кожен тип токена має рівно один mint.
  • Token-акаунт — пов'язаний з конкретним mint та власником. Зберігає поточний баланс. Один власник може мати багато token-акаунтів для одного й того ж mint (хоча зазвичай це один акаунт).
  • Associated Token Account (ATA) — детерміновано згенерований token-акаунт за формулою з owner та mint. Це стандартний спосіб зберігання токенів для користувачів.

Операції з токенами

Основні інструкції SPL Token Program:

  • InitializeMint — створює новий mint-акаунт із заданими параметрами.
  • MintTo — випускає токени на вказаний token-акаунт.
  • Transfer / TransferChecked — переміщує токени між token-акаунтами. TransferChecked додатково перевіряє кількість знаків після коми.
  • Burn — знищує токени, зменшуючи загальну пропозицію.
  • FreezeAccount / ThawAccount — блокує або розблоковує token-акаунт (якщо mint має увімкнений freeze authority).
  • CloseAccount — закриває token-акаунт, повертаючи rent власнику.

Token Program у контексті програм

Ваша програма на Solana не «містить» токени. Вона лише керує token-акаунтами через виклики до SPL Token Program. Програма може вимагати від користувача передати token-акаунт у інструкцію, перевірити його mint і потім викликати TransferChecked через CPI.

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

  • Передача mint-акаунта замість token-акаунта у функцію, що очікує token-акаунт — призводить до помилки декодування.
  • Використання Transfer замість TransferChecked — безпечніше завжди використовувати TransferChecked, оскільки він верифікує decimals.
  • Ігнорування rent-exempt вимоги — token-акаунти повинні мати мінімальний баланс для exemption від rent.

Token Extensions: огляд для початківця

Що таке Token Extensions

Token Extensions — це розширена версія SPL Token Program, відома як Token-2022 (адреса: TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb). Вона повністю зворотно сумісна з оригінальним Token Program, але додає можливість приєднувати розширення до mint- та token-акаунтів.

Основні розширення

  • Transfer Hook — дозволяє викликати сторонню програму під час кожної передачі токена (корисно для податків, комісій, whitelisting).
  • Transfer Fee — автоматично стягує відсоток при кожній передачі.
  • Metadata — зберігає name, symbol, uri безпосередньо в mint-акаунті, без потреби в окремій програмі метаданих.
  • Permanent Delegate — дозволяє делегату переміщувати токени з будь-якого акаунта без підпису власника.
  • Confidential Transfers — приховані баланси та суми передач.
  • Interest Bearing — токен із змінною процентною ставкою.

Як використовувати Token Extensions

У CLI використовуйте прапорець --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb при створенні токена. У TypeScript SDK імпортуйте функції з @solana/spl-token — бібліотека підтримує Token-2022 починаючи з версії 0.3.x через параметр programId.

Концептуальний приклад створення токена з метаданими через TypeScript (Devnet, @solana/spl-token 0.3.x):

Примітка: Це концептуальний приклад для розуміння структури. Для production використовуйте перевірені утиліти з офіційного репозиторію spl-token.

Ключова відмінність: при створенні mint через Token-2022 ви передаєте додаткові інструкції для ініціалізації розширень одразу після InitializeMint2.

Коли обрати Token-2022 замість Token

  • Вам потрібні метадані безпосередньо в токені (RWA-токени, стейблкоїни).
  • Потрібен Transfer Hook для кастомної логіки при передачах.
  • Потрібна комісія за передачу (Transfer Fee).
  • Ви створюєте новий проєкт і не маєте зворотної сумісності з існуючими token-акаунтами.

Якщо вам потрібен просто звичайний fungible токен без додаткової логіки — оригінальний Token Program достатній.

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

  • Створення token-акаунта через оригінальний Token Program для mint, що належить Token-2022 — акаунти не сумісні між програмами.
  • Ігнорування додаткового простору, потрібного для розширень — mint-акаунт з розширеннями потребує більшого розміру, отже більшого deposit для rent-exempt.
  • Передача programId оригінального Token Program у функції Token-2022 — призводить до невідповідності типів.

Як створити токен на Devnet

Підготовка

Переконайтеся, що ви на Devnet і маєте SOL для оплати транзакцій:

  • solana config set --url https://api.devnet.solana.com
  • solana airdrop 2 — отримайте 2 SOL на Devnet (якщо airdrop недоступний, використовуйте faucet з офіційного сайту Solana).

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

Створіть mint-акаунт з 9 знаками після коми:

spl-token create-token --decimals 9

CLI поверне адресу mint-акаунта. Збережіть її.

Mint-нення токенів

Спочатку створіть associated token account для вашого гаманця:

spl-token create-account <MINT_ADDRESS>

Потім випустіть токени:

spl-token mint <MINT_ADDRESS> 1000

Очікуваний результат: 1000 токенів (з урахуванням 9 decimals — це фактично 1000 * 10^9 найменших одиниць) на вашому token-акаунті.

Перевірка результату

spl-token balance <MINT_ADDRESS>

Має показати 1000.

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

  • Недостатньо SOL для rent-exempt token-акаунта — переконайтеся, що на гаманці є щонайменше 0.002 SOL понад витрати на створення mint.
  • Неправильний decimals — після створення mint змінити decimals неможливо. Якщо вказали 0 замість 9, доведеться створювати новий токен.
  • Спроба mint-нути більше, ніж дозволяє supply — за замовчуванням supply необмежений, але якщо ви вказали --supply, перевищення викличе помилку.

PDA простими словами для розробника

Що таке PDA

PDA (Program Derived Address) — це адреса акаунта, яка генерується детерміновано з набору seeds (довільних байтів) та адреси програми. Головна властивість: PDA не має відповідного приватного ключа. Це адреса, яка математично гарантовано лежить поза кривою ed25519, тому жоден ключ не може її підписати.

Як генерується PDA

Алгоритм: береться рядок seeds, до неї додається program_id, обчислюється SHA-256. Якщо результат — точка на кривій ed25519, до seeds додається байт bump (починаючи з 255) і процес повторюється. Перший bump, що дає точку поза кривою, стає частиною PDA.

Формула: PDA = SHA-256(seeds || program_id || bump), де bump — найменше значення від 255 вниз, що дає off-curve результат.

Навіщо потрібен PDA

  • Власність програми над даними. Програма може створювати акаунти, які належать їй самій, а не користувачу. Це означає, що лише програма може модифікувати ці дані.
  • Детерміновані адреси. Будь-хто, хто знає seeds та program_id, може обчислити PDA без запиту до блокчейну. Це дозволяє знаходити акаунти за логікою (наприклад, «акаунт конфігурації для цього токена»).
  • Підпис від імені програми. Програма може «підписати» інструкцію від імені PDA через invoke_signed у CPI.

PDA як signer

Коли програма викликає іншу програму через CPI і передає PDA як signer, вона надає bump разом з інструкцією. Runtime Solana перевіряє, що PDA дійсно належить цій програмі і bump правильний. Це дозволяє PDA виконувати роль підписанта без приватного ключа.

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

  • Зберігання bump у стані програми замість передачі як аргумент — це припустимо, але не забувайте перевіряти, що збережений bump збігається з обчисленим.
  • Використання змінних seeds — якщо seeds залежать від даних, які можуть змінюватися, PDA буде різним у різні моменти. Seeds мають бути стабільними.
  • Спроба підписати PDA без invoke_signed — runtime відхилить транзакцію, оскільки PDA не має приватного ключа.

Як створити PDA у програмі

Визначення seeds

Seeds — це масив байтів, який однозначно ідентифікує PDA. Типові патерни:

  • b"config" — для глобального акаунта конфігурації програми.
  • [b"vault", mint.key().as_ref()] — для акаунта, прив'язаного до конкретного токена.
  • [user.key().as_ref(), b"profile"] — для акаунта, прив'язаного до користувача.

Правило: seeds мають бути унікальними в межах однієї програми, щоб різні PDA не колізували.

Створення PDA через Anchor

У Anchor PDA оголошується через атрибут #[account] з полями seeds та bump:

Приклад (Anchor 0.30.x, Rust):

#[derive(Accounts)]
pub struct InitializeVault<'info> {
  #[account(
    init,
    payer = authority,
    space = 8 + 32 + 8,
    seeds = [b"vault", mint.key().as_ref()],
    bump
  )]
  pub vault: Account<'info, Vault>,
  pub mint: Account<'info, Mint>,
  #[account(mut)]
  pub authority: Signer<'info>,
  pub system_program: Program<'info, System>,
}

Очікуваний результат: Anchor автоматично обчислить PDA, створить акаунт, сплатить rent та збереже bump у vault-акаунті (якщо структура має поле bump: u8).

Знаходження існуючого PDA

Коли PDA вже створено і ви звертаєтесь до нього в іншій інструкції:

#[derive(Accounts)]
pub struct UseVault<'info> {
  #[account(
    seeds = [b"vault", mint.key().as_ref()],
    bump = vault.bump
  )]
  pub vault: Account<'info, Vault>,
  pub mint: Account<'info, Mint>,
}

Anchor перевірить, що переданий акаунт дійсно відповідає PDA з цими seeds і що bump збігається.

Використання PDA для зберігання стану

PDA-акаунти зберігають серіалізовані дані вашої структури. Розмір (space) розраховується як: 8 байтів (дискримінатор Anchor) + сума розмірів полів. Наприклад, для структури з Pubkey (32 байти) та u64 (8 байтів) потрібно 8 + 32 + 8 = 48 байтів.

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

  • Невірний розмір space — якщо вказати замало, ініціалізація завершиться помилкою. Якщо забагато — переплата rent.
  • Забуття поля bump у структурі при використанні bump = vault.bump — Anchor не знайде поле для перевірки.
  • Передача seeds у неправильному порядку — [b"vault", mint.as_ref()] і [mint.as_ref(), b"vault"] дають різні PDA.

Що таке CPI у Solana

Означення CPI

CPI (Cross-Program Invocation) — це механізм, за якого одна програма на Solana викликає інструкцію іншої програми в межах тієї ж транзакції. Це аналог виклику функції з іншого модуля, але між різними програмами з різною логікою перевірки.

Як працює CPI

Коли програма A викликає програму B через CPI:

  1. Програма A формує інструкцію для програми B (якби вона була зовнішньою транзакцією).
  2. Викликає invoke або invoke_signed (якщо потрібно підписати PDA).
  3. Runtime Solana передає інструкцію програмі B, додаючи програму A до ланцюжка викликів.
  4. Програма B виконує інструкцію з тими ж акаунтами, що передала програма A.
  5. Після завершення B, управління повертається до A.

Глибина вкладеності CPI обмежена (зазвичай до 4 рівнів).

Обмеження CPI

  • Глибина вкладеності: максимальна глибина ланцюжка CPI — 4. Перевищення призведе до помилки.
  • Обчислювальний бюджет: кожен виклик CPI споживає compute units з загального бюджету транзакції (зараз 200 000 CU на Devnet).
  • Права підпису: програма може передати лише ті права, які має. Вона не може підписати акаунт користувача — лише свої PDA.
  • Зміна акаунтів: програма не може передати в CPI акаунт, який є writable у поточній інструкції, але був readonly у попередній (зміна mutability заборонена).

Типові сценарії CPI

  • Ваша програма викликає SPL Token Program для mint, transfer або burn токенів.
  • Ваша програма викликає System Program для створення акаунта (через CPI, а не через інструкцію користувача).
  • Ваша програма викликає іншу кастомну програму (наприклад, AMM для свапу).

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

  • Передача неправильного набору акаунтів у CPI — програма-ціль отримає акаунти, які ви передали, і якщо когось не вистачає, інструкція впаде.
  • Забуття вказати correct signer — якщо CPI потребує підпису PDA, а ви використали invoke замість invoke_signed, транзакція буде відхилена.
  • Перевищення глибини CPI — зазвич виникає при рекурсивних викликах або складних ланцюжках DeFi-протоколів.

Як викликати іншу програму через CPI

Підготовка акаунтів для CPI

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

CPI через Anchor

Anchor спрощує CPI через макроси CpiContext. Приклад виклику System Program для створення акаунта (Anchor 0.30.x):

Концептуальний приклад:

pub fn create_account(ctx: Context<CreateAccount>, space: u64) -> Result<()> {
  let cpi_accounts = system_program::CreateAccount {
    from: ctx.accounts.payer.to_account_info(),
    to: ctx.accounts.new_account.to_account_info(),
  };
  let cpi_program = ctx.accounts.system_program.to_account_info();
  anchor_lang::system_program::create_account(
    CpiContext::new(cpi_program, cpi_accounts),
    space,
  )
}

invoke_signed

У чистому Rust (без Anchor обгортки) для CPI з підписом PDA використовується invoke_signed:

Концептуальний приклад (Rust, без Anchor):

invoke_signed(
  &instruction,
  &account_infos,
  &[&seeds[..], &[bump]],
)?;

Останній аргумент — масив signer seeds. Runtime перевірить, що кожен вказаний PDA дійсно належить програмі, що викликає invoke_signed.

Обробка результату CPI

Якщо цільова програма повертає помилку, invoke / invoke_signed поверне ProgramError. У Anchor ви можете обгорнути CPI у стандартний ? оператор, і помилка пошириться далі. Для кастомної обробки використовуйте .map_err().

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

  • Невідповідність типу акаунта — передача AccountInfo замість конкретного типу Account у CpiContext.
  • Забуття system_program у структурі accounts — Anchor не зможе знайти програму для CPI.
  • Неправильний порядок signer seeds у invoke_signed — seeds мають точно відповідати тим, що використовувалися для генерації PDA.

Як передати токен через програму

Що потрібно для токен-транзакції

Для передачі токенів через SPL Token Program потрібні:

  • Відправник: token-акаунт (writable), його власник (signer).
  • Одержувач: token-акаунт (writable).
  • Mint: акаунт mint токена (для TransferChecked).
  • Token Program: програма, що обробляє інструкцію.

Передача через Anchor

Концептуальний приклад (Anchor 0.30.x):

pub fn transfer_tokens(ctx: Context<TransferTokens>, amount: u64) -> Result<()> {
  let cpi_accounts = token::TransferChecked {
    from: ctx.accounts.from_ata.to_account_info(),
    to: ctx.accounts.to_ata.to_account_info(),
    mint: ctx.accounts.mint.to_account_info(),
    authority: ctx.accounts.authority.to_account_info(),
  };
  let cpi_program = ctx.accounts.token_program.to_account_info();
  token::transfer_checked(
    CpiContext::new(cpi_program, cpi_accounts),
    amount,
    ctx.accounts.mint.decimals,
  )
}

Очікуваний результат: вказана кількість токенів переміщується з from_ata на to_ата. Баланс відправника зменшується, одержувача — збільшується.

Передача від імені PDA

Якщо token-акаунт належить PDA (наприклад, vault), програма має використати CpiContext::new_with_signer:

let seeds = &[b"vault", ctx.accounts.mint.key().as_ref(), &[ctx.accounts.vault.bump]];
let signer_seeds = &[&seeds[..]];
let cpi_context = CpiContext::new_with_signer(
  ctx.accounts.token_program.to_account_info(),
  cpi_accounts,
  signer_seeds,
);
token::transfer_checked(cpi_context, amount, ctx.accounts.mint.decimals)?;

Це дозволяє PDA виступати як authority для token-акаунта, який йому належить.

Перевірка результату

Після виклику перевірте баланси через CLI:

spl-token balance <MINT_ADDRESS> --owner <WALLET_ADDRESS>

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

  • Передача amount у найменших одиницях замість з урахуванням decimals — якщо decimals = 9, то 1 токен = 1000000000. Передача 1 означає передачу 0.000000001 токена.
  • Використання Transfer замість TransferChecked — менш безпечно, оскільки не верифікує decimals.
  • Забуття вказати mint.decimals у transfer_checked — Anchor вимагає цей параметр явно.

Як mint-нути токен через Anchor

Що таке mint

Mint (емісія) — це процес створення нових одиниць токена та зарахування їх на token-акаунт. Цю операцію може виконати лише акаунт, який вказаний як mint authority у mint-акаунті.

Mint через Anchor CPI

Концептуальний приклад (Anchor 0.30.x):

pub fn mint_tokens(ctx: Context<MintTokens>, amount: u64) -> Result<()> {
  let cpi_accounts = token::MintTo {
    mint: ctx.accounts.mint.to_account_info(),
    to: ctx.accounts.token_account.to_account_info(),
    authority: ctx.accounts.authority.to_account_info(),
  };
  let cpi_program = ctx.accounts.token_program.to_account_info();
  token::mint_to(CpiContext::new(cpi_program, cpi_accounts), amount)
}

Mint authority як PDA

У більшості реальних застосунків mint authority — це PDA програми, а не зовнішній гаманець. Це гарантує, що лише програма може випускати токени:

Під час створення mint-акаунта встановіть authority на PDA:

let mint = token::initialize_mint2(
  CpiContext::new(
    ctx.accounts.token_program.to_account_info(),
    token::InitializeMint2 {
      mint: ctx.accounts.mint.to_account_info(),
    },
  ),
  9,
  &ctx.accounts.vault.key(),
  None,
)?;

Потім при mint-ненні використовуйте CpiContext::new_with_signer з seeds vault PDA.

Перевірка результату

spl-token supply <MINT_ADDRESS> — має показати збільшену загальну пропозицію.
spl-token balance <MINT_ADDRESS> — має показати збільшений баланс.

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

  • Mint authority не збігається з переданим authority у MintTo — переконайтеся, що при ініціалізації mint ви встановили правильний authority.
  • Забуття передати signer seeds для PDA-mint-authority — без new_with_signer CPI не зможе підтвердити права.
  • Перевищення max supply — якщо при створенні mint було вказано обмеження supply, mint_to впаде при перевищенні.

Як перевірити баланс токена на Devnet

Через CLI

Баланс конкретного гаманця для вказаного токена:

spl-token balance <MINT_ADDRESS>

Якщо у вас кілька token-акаунтів для цього mint, вкажіть власника:

spl-token balance <MINT_ADDRESS> --owner <WALLET_ADDRESS>

Загальна пропозиція токена:

spl-token supply <MINT_ADDRESS>

Через TypeScript SDK

Приклад (@solana/spl-token 0.3.x, @solana/web3.js 1.95.x, Devnet):

import { Connection, PublicKey } from "@solana/web3.js";
import { getAccount, getMint } from "@solana/spl-token";

const connection = new Connection("https://api.devnet.solana.com", "confirmed");
const mintAddress = new PublicKey("<MINT_ADDRESS>");
const walletAddress = new PublicKey("<WALLET_ADDRESS>");

// Отримати інформацію про mint
const mintInfo = await getMint(connection, mintAddress);
console.log("Decimals:", mintInfo.decimals);
console.log("Supply:", mintInfo.supply.toString());

// Отримати баланс через associated token account
const ata = await getAssociatedTokenAddress(mintAddress, walletAddress);
const tokenAccount = await getAccount(connection, ata);
console.log("Balance:", tokenAccount.amount.toString());

Очікуваний результат: виведення decimals, загальної пропозиції та балансу у найменших одиницях. Для відображення в людському форматі поділіть amount на 10^decimals.

Через Explorer

Відкрийте explorer.solana.com, перемкніть на Devnet, введіть адресу token-акаунта. На сторінці акаунта буде вказано баланс та mint. Або введіть адресу гаманця та перейдіть до вкладки «Token Holdings».

Баланс у контексті програми

У програмі на Anchor ви можете прочитати баланс token-акаунта через поле amount типу Account<'info, TokenAccount>. Це корисно для перевірки перед виконанням логіки:

let balance = ctx.accounts.token_account.amount;
require!(balance >= amount, ErrorCode::InsufficientBalance);

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

  • Читання балансу mint-акаунта замість token-акаунта — mint-акаунт зберігає supply, а не баланс конкретного власника.
  • Ігнорування decimals при відображенні — баланс 1000000000 з decimals 9 — це 1 токен, а не мільярд.
  • Запит балансу для ATA, який ще не створено — getAccount поверне помилку. Спочатку перевірте існування через try/catch або використовуйте getAssociatedTokenAddressSync з перевіркою.

Типові помилки PDA та CPI у Solana

Помилки PDA

  • Seeds mismatch: найчастіша помилка. Програма обчислила PDA з одних seeds, а клієнт передав акаунт, згенерований з інших. Рішення: завжди використовуйте однакову логіку seeds на клієнті та в програмі.
  • InvalidSeeds: передані seeds не відповідають жодному PDA цієї програми. Перевірте порядок та вміст seeds.
  • Bump не збігається: якщо ви зберігаєте bump у стані, переконайтеся, що він записаний коректно під час ініціалізації.

Помилки CPI

  • Missing required signature: CPI потребує підпису, але ви використали invoke замість invoke_signed. Перевірте, чи цільова програма вимагає signer для цього акаунта.
  • Account not writable: акаунт, який CPI хоче змінити, переданий як readonly. Додайте mut у Anchor або вкажіть is_writable: true в AccountMeta.
  • Illegal owner: акаунт, який ви передаєте в CPI, належить не тій програмі. Наприклад, token-акаунт, створений через Token-2022, не підійде для CPI до оригінального Token Program.

Помилки Token CPI

  • InsufficientFunds: спроба перевести більше токенів, ніж є на балансі. Перевіряйте amount до виклику CPI.
  • MintMismatch: token-акаунти відправника та одержувача належать різним mint. Переконайтеся, що обидва пов'язані з одним mint.
  • AuthorityTypeMismatch: переданий authority не є mint authority або не має потрібних прав. Перевірте, хто встановлений як authority у mint-акаунті.

Як діагностувати

  1. Читайте error logs: solana logs на локальному validator або деталі транзакції в Explorer показують точний код помилки та програму, що її повернула.
  2. Використовуйте anchor test -- --features debug: увімкне додаткове логування в Anchor програмах.
  3. Перевіряйте акаунти через CLI: solana account <ADDRESS> покаже owner, data, lamports. Переконайтеся, що owner правильний.
  4. Зменшуйте складність: якщо CPI ланцюжок складний, спочатку перевірте кожен виклик окремо.

Профілактика

  • Завжди використовуйте TransferChecked замість Transfer — це виловить помилку неправильних decimals раніше.
  • Завжди передавайте token_program як окремий акаунт у структурах Anchor — це дозволяє підтримувати як Token Program, так і Token-2022.
  • Використовуйте constraint у Anchor для явної перевірки mint та owner перед CPI.
  • Тестуйте PDA-генерацію на клієнті та в програмі з однаковими seeds — розбіжність у одному байті дасть різні адреси.
  • Не ігноруйте попередження компілятора про unused variables у структурах accounts — кожен акаунт у структурі передається в програму і впливає на перевірки.

Після опанування токенів, PDA та CPI ви маєте повний набір інструментів для написання програм, що керують активами на Solana. Наступний логічний крок — застосувати ці знання в практичних проєктах, де кожна з цих трьох концепцій працює разом.

Джерела