Версії для прикладів у «Як mint-нути токен через Anchor» перевірено 2 серпня 2026 року. Стабільна гілка Anchor v1 має релізи 1.0.x і орієнтується на Solana 3.x; Anchor v2 у документації позначений як alpha. Приклади для Anchor 0.29–0.32 залишаються лише відтворюваними прикладами для зафіксованого legacy-середовища: їх не слід переносити в новий проєкт без міграції залежностей і повторного тестування.
Ця інструкція проведе вас через створення нових одиниць токена на Solana за допомогою Anchor. Ви отримаєте робочий код, зрозумієте роль кожного облікового запису та зможете перевірити результат на Devnet.
Середовище: Solana CLI 1.18.x, Anchor 0.30.x, Rust 1.75+, кластер Devnet.
Передумови: ініціалізований Anchor-проєкт, підключений до Devnet, встановлений пакет anchor-spl версії 0.30.x.
Очікуваний результат: виклик інструкції програми, яка створює визначену кількість токенів на вказаному токен-рахунку.
Що таке mint
Створення нових одиниць токена
Mint — це обліковий запис у мережі Solana, який визначає тип токена: його назву, символ, кількість знаків після коми та загальну пропозицію. Сам по собі mint не зберігає жодних одиниць токена — він лише описує правила їх створення. Коли кажуть «mint-нути токен», мають на увазі збільшення значення supply цього облікового запису та одночасне зарахування створених одиниць на конкретний токен-рахунок.
У термінах SPL Token Program операція mint — це виклик інструкції MintTo, яка приймає три облікові записи: сам mint, токен-рахунок призначення та authority, що має право на створення нових одиниць.
Mint authority та його роль
Кожен mint при створенні отримує параметр mint_authority. Це обліковий запис, який має право викликати інструкцію MintTo для цього mint. Без підпису цього authority жоден обліковий запис не зможе створити нові одиниці токена.
Якщо mint_authority не встановлено (значення None), токен є незмінним — його supply фіксований назавжди. Це корисно для токенів із фіксованою емісією. Якщо ж authority встановлено, цей запис може створювати нові одиниці доти, доки не буде явно відкликаний через інструкцію SetAuthority.
Mint через Anchor CPI
token::mint_to у CpiContext
Anchor не реалізує логіку токенів самостійно — він делегує її SPL Token Program через Cross-Program Invocation (CPI). Функція token::mint_to із модуля anchor_spl::token формує правильну інструкцію MintTo і передає її до Token Program.
Базова структура інструкції в Anchor виглядає так:
use anchor_lang::prelude::*;
use anchor_spl::token::{self, Mint, Token, TokenAccount};
#[derive(Accounts)]
pub struct MintTokens<'info> {
#[account(mut)]
pub mint: Account<'info, Mint>,
#[account(mut)]
pub destination: Account<'info, TokenAccount>,
pub authority: Signer<'info>,
pub token_program: Program<'info, Token>,
}
pub fn handler(ctx: Context<MintTokens>, amount: u64) -> Result<()> {
let cpi_accounts = token::MintTo {
mint: ctx.accounts.mint.to_account_info(),
to: ctx.accounts.destination.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)?;
Ok(())
}
Цей приклад підходить для навчальних цілей, де authority — це зовнішній підписувач. У реальних застосунках authority зазвичай є PDA, що дозволяє програмі mint-нути токени без участі користувача з правами підпису.
Визначення mint, destination, authority
Кожен обліковий запис у структурі виконує точну роль:
- mint — обліковий запис типу Mint, чий supply збільшиться. Позначений mut, оскільки Token Program змінить його стан.
- destination — токен-рахунок типу TokenAccount, куди зарахуються створені одиниці. Повинен мати той самий mint, що й перший аргумент. Також mut.
- authority — обліковий запис, що підписує транзакцію і збігається з mint_authority облікового запису mint.
- token_program — програма, що виконує саму інструкцію (SPL Token Program).
Зверніть увагу: Anchor автоматично перевіряє, що destination.mint збігається з переданим mint, завдяки типізації Account<'info, TokenAccount>. Це виключає помилку зарахування на токен-рахунок іншого токена.
Mint authority як PDA
Чому програма повинна бути mint authority
Якщо authority — це звичайний ключ користувача, то для кожного mint потрібна окрема транзакція з підписом цього користувача. Це неприйнятно для децентралізованих застосунків, де токени мають створюватися програмно — наприклад, як нагорода за дію, стейкінг або внесок у ліквідність.
Рішення — зробити mint authority програмно-похідним адресом (PDA). PDA належить програмі, не має відповідного приватного ключа, але програма може підписати від його імені через invoke_signed. Anchor абстрагує цей механізм у CpiContext::new_with_signer.
Налаштування authority при створенні токена
Щоб PDA міг mint-нути токени, його потрібно вказати як mint_authority ще на етапі створення mint. Ось приклад інструкції ініціалізації:
#[derive(Accounts)]
pub struct InitializeMint<'info> {
#[account(
init,
payer = payer,
mint::decimals = 9,
mint::authority = authority
)]
pub mint: Account<'info, Mint>,
/// CHECK: PDA, що слугує mint authority
#[account(
seeds = [b"mint_auth"],
bump
)]
pub authority: UncheckedAccount<'info>,
#[account(mut)]
pub payer: Signer<'info>,
pub token_program: Program<'info, Token>,
pub system_program: Program<'info, System>,
pub rent: Sysvar<'info, Rent>,
}
pub fn handler(ctx: Context<InitializeMint>) -> Result<()> {
Ok(())
}
Обмеження mint::authority = authority у макросі init автоматично встановлює PDA як mint authority при створенні облікового запису. Тепер інструкція mint з PDA виглядає так:
#[derive(Accounts)]
pub struct MintViaPda<'info> {
#[account(mut)]
pub mint: Account<'info, Mint>,
#[account(mut)]
pub destination: Account<'info, TokenAccount>,
/// CHECK: PDA — mint authority
#[account(
seeds = [b"mint_auth"],
bump
)]
pub authority: UncheckedAccount<'info>,
pub token_program: Program<'info, Token>,
}
pub fn handler(ctx: Context<MintViaPda>, amount: u64, bump: u8) -> Result<()> {
let seeds = &[b"mint_auth".as_ref(), &[bump]];
let signer_seeds = &[&seeds[..]];
let cpi_accounts = token::MintTo {
mint: ctx.accounts.mint.to_account_info(),
to: ctx.accounts.destination.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_with_signer(cpi_program, cpi_accounts, signer_seeds),
amount
)?;
Ok(())
}
Ключова різниця — CpiContext::new_with_signer замість CpiContext::new. Масив seeds має точно збігатися з тим, що використовувався при створенні PDA. Значення bump передається як аргумент інструкції або отримується через ctx.bumps.get("authority") у Anchor 0.30.x.
Перевірка результату
Перевірка supply після mint
Після виклику інструкції на Devnet переконайтеся, що supply оновився. Використайте CLI:
solana token supply <MINT_ADDRESS> --url devnet
Очікуваний результат — значення supply дорівнює сумі всіх викликів mint_to для цього mint. Якщо ви mint-нули 1 000 000 одиниць із 9 знаками після коми, CLI покаже 1000000000000 (базові одиниці).
Також можна перевірити стан токен-рахунку призначення:
solana token account-info <TOKEN_ACCOUNT_ADDRESS> --url devnet
Поле amount має відповідати замінтеним одиницям.
Обмеження supply
SPL Token Program не має вбудованого параметра max_supply. Обмеження емісії — це логіка вашої програми. Типовий підхід: зберігати лічильник у спеціальному обліковому записі програми та порівнювати його з допустимим максимумом перед кожним викликом mint_to.
pub fn handler(ctx: Context<MintViaPda>, amount: u64, bump: u8) -> Result<()> {
let current_supply = ctx.accounts.mint.supply;
let max_supply: u64 = 1_000_000_000_000; // 1000 токенів з 9 decimals
require!(
current_supply.checked_add(amount).unwrap() <= max_supply,
MyError::SupplyExceeded
);
// ... далі mint_to
}
Після досягнення ліміту програма може відкликати mint authority через token::set_authority з параметром AuthorityType::MintTokens та значенням None, що зробить емісію неможливою назавжди.
Типові помилки
Mint to zero (недопустима кількість)
SPL Token Program відхиляє інструкцію MintTo, якщо amount дорівнює нулю. Це не помилка Anchor — це обмеження на рівні Token Program. Якщо ваша логіка обчислює amount динамічно і може дати нуль, додайте перевірку до виклику CPI:
require!(amount > 0, MyError::ZeroAmount);
Без цієї перевірки транзакція впаде з помилкою Token Program, що ускладнює діагностику, оскільки текст помилки буде загальним.
Невірний mint authority
Ця помилка виникає, коли обліковий запис, переданий як authority у MintTo, не збігається з mint_authority, збереженим у обліковому записі mint. Поширені причини:
- Збій у seeds PDA. Насіння, використане при створенні mint (mint::authority = authority), має точно збігатися з насінням у інструкції mint. Навіть одна відмінна байтова послідовність дасть іншу адресу.
- Передано зовнішній підписувач замість PDA. Якщо mint було створено з PDA як authority, передання Signer у полі authority призведе до помилки, навіть якщо цей підписувач ініціює транзакцію.
- Authority було відкликано. Якщо раніше було викликано set_authority з None, mint більше не має authority, і жоден обліковий запис не зможе виконати MintTo.
У всіх цих випадках Token Program поверне помилку AuthorityType або InvalidAuthority. Перевірте стан mint через CLI, щоб підтвердити поточний mint_authority:
solana token metadata <MINT_ADDRESS> --url devnet
Після успішного mint на Devnet наступний логічний крок — перевірити баланс токена на Devnet, щоб переконатися, що одиниці зараховані коректно.