Priority fees у Solana — це механізм, що дозволяє транзакції отримати вищий пріоритет у черзі лідера під час конгестії мережі. Без правильно налаштованих priority fees ваші транзакції можуть затримуватися на секунди або хвилини, що для production-застосунків зазвичай неприйнятно. Нижче — покрокова інструкція з налаштування, регулювання та моніторингу priority fees.
Як працює priority fee у Solana
Починаючи з впровадження концепції compute unit price, Solana розділила комісію за транзакцію на дві складові:
- Base fee — фіксована мінімальна комісія за підпис та кожен байт транзакції. Ця частина завжди однакова для транзакцій однакового розміру.
- Priority fee — додаткова комісія, що розраховується як добуток compute unit price (у micro-lamports за одиницю обчислення) на compute unit limit (максимальну кількість обчислювальних одиниць, яку транзакція може використати).
Лідер ноди, яка формує блок, сортує транзакції у своєму пулі за спаданням priority fee. Транзакції з вищим значенням потрапляють до блоку першими. Якщо блок заповнений, транзакції з низьким пріоритетом залишаються в пулі і чекають наступного слоту.
Важливий нюанс: priority fee сплачується лише за ті compute units, які транзакція фактично використала, а не за заявлений ліміт. Проте при сортуванні лідер орієнтується на максимальний можливий платіж — добуток compute unit price на compute unit limit. Тому завищений compute unit limit штучно підвищує ефективний пріоритет, але водночас резервує більше місця у блоці, що може знизити загальну пропускну здатність.
Формула розрахунку
Priority fee (lamports) = computeUnitPrice × computeUnitLimit
Де computeUnitPrice вимірюється в micro-lamports (1 micro-lamport = 0.000001 lamport). Наприклад, computeUnitPrice = 100 000 micro-lamports (0.0001 lamport) при computeUnitLimit = 200 000 дасть максимальний priority fee = 20 lamports.
Визначення оптимальної величини fee
Оптимальне значення — це мінімальний computeUnitPrice, за якого ваша транзакція стабільно потрапляє до блоку протягом прийнятного для вашого застосунку часу. Визначення цього значення складається з кількох кроків.
Крок 1. Визначте compute unit limit вашої транзакції
Перед тим як встановлювати ціну, потрібно знати ліміт. Є два підходи:
- Simulate з auto-limit. Використовуйте RPC-метод
simulateTransactionз параметромreplaceRecentBlockhashта аналізуйте полеunitsConsumed у відповіді. Додайте запас 10–20%. - Фіксований ліміт. Якщо ваша інструкція детермінована, встановіть ліміт на основі попередніх симуляцій. Це запобігає ситуаціям, коли зміна в залежностях непомітно збільшує споживання.
Не встановлюйте compute unit limit у 1 400 000 (максимум) «на всякий випадок» — це штучно завищує ефективний пріоритет і створює тиск на мережу.
Крок 2. Отримайте актуальні дані про конгестію
Використовуйте RPC-метод getRecentPrioritizationFees, передавши масив адрес акаунтів, які ваша транзакція зчитує або записує. Метод повертає масив об'єктів з полями slot та prioritizationFee (у micro-lamports за compute unit) для останніх 150 слотів.
Приклад запиту:
getRecentPrioritizationFeesз аргументом["Account1Address", "Account2Address"]
Фільтруйте результати за актуальними слотами (наприклад, останні 20–30 слотів) та обчисліть перцентиль. Медіана (50-й перцентиль) зазвичай дає достатній пріоритет для ненайконгестованіших періодів. 75-й або 90-й перцентиль підходить для критичних транзакцій під час спайків навантаження.
Крок 3. Встановіть compute unit price
На основі обраного перцентиля встановіть computeUnitPrice у вашій транзакції. У @solana/web3.js це робиться через поле computeUnitPrice об'єкта ComputeBudgetProgram.setComputeUnitPrice.
Очікуваний результат: транзакція підтверджується протягом 1–2 слотів (400–800 мс) за нормального навантаження та протягом 3–5 слотів під час помірної конгестії.
Типова помилка: орієнтація на середнє значення
Середнє значення prioritizationFee спотворюється викидами від MEV-ботів, які встановлюють екстремально високі fees. Завжди використовуйте перцентилі, а не середнє.
Динамічне регулювання fee залежно від конгестії
Статичне значення computeUnitPrice не підходить для production. Конгестія мережі змінюється хвилина до хвилини, і ваш застосунок має адаптуватися.
Стратегія зворотного зв'язку за результатом
Найпростіша і найнадійніша стратегія для більшості застосунків:
- Початкове значення. Встановіть computeUnitPrice на рівні 75-го перцентиля з
getRecentPrioritizationFees. - Відправка та моніторинг. Відправте транзакцію та підпишіться на підтвердження через
confirmTransactionз стратегієюfinalizedабоconfirmedзалежно від ваших вимог до безпеки. - Таймаут. Якщо транзакція не підтверджена за N слотів (наприклад, 8 слотів ≈ 3.2 секунди), скасуйте її через
sendTransactionз тим самимblockhashале вищим computeUnitPrice (наприклад, множите на 1.5). - Обмеження спроб. Встановіть максимальну кількість повторних спроб (наприклад, 3) та максимальний computeUnitPrice (наприклад, 10 000 000 micro-lamports), щоб уникнути нескінченного зростання витрат.
Стратегія періодичного опитування
Для сервісів з високою частотою транзакцій (маркетмейкери, арбітражні боти, інфраструктурні сервіси):
- Кожні 400–800 мс викликайте
getRecentPrioritizationFeesдля релевантних акаунтів. - Обчислюйте 75-й перцентиль по ковзному вікну з останніх 20 слотів.
- Кешуйте результат і використовуйте для всіх транзакцій у наступному інтервалі.
- Додайте гістерезис: змінюйте computeUnitPrice лише якщо нове значення відрізняється від поточного більше ніж на 20%. Це запобігає надмірній волатильності.
Межі застосування динамічного регулювання
- Не підходить для транзакцій, що залежать від точного порядку виконання (наприклад, серія транзакцій у рамках одного бізнес-процесу). У таких випадках використовуйте фіксований високий computeUnitPrice для всієї серії.
- Не підходить якщо ваш RPC-провайдер кешує відповіді
getRecentPrioritizationFeesі повертає застарілі дані. Перевірте документацію провайдера або вимірюйте затримку даних порівняно зgetSlot.
План відкату
Якщо динамічне регулювання спричиняє нестабільність (наприклад, транзакції постійно таймаутяться через коливання fee): встановіть фіксований computeUnitPrice на рівні 90-го перцентиля, заміряного під час пікового навантаження за останні 24 години. Це консервативний підхід із вищими витратами, але передбачуваною поведінкою.
Моніторинг ефективності priority fees
Без моніторингу неможливо визначити, чи ваші priority fees оптимальні чи ви переплачуєте.
Ключові метрики
| Метрика | Як вимірювати | Цільове значення |
|---|---|---|
| Час підтвердження (confirmation latency) | Різниця між blockTime блоку, що містить транзакцію, та часом відправки |
< 2 слотів для звичайних транзакцій |
| Співвідношення фактичного та максимального priority fee | transaction.meta.fee мінус base fee, поділене на computeUnitPrice × computeUnitLimit |
Близько 1.0 (означає, що compute unit limit адекватний) |
| Частота повторних спроб (retry rate) | Кількість транзакцій, що потребували повторної відправки, поділена на загальну кількість | < 5% |
| Середній computeUnitPrice за період | Середнє значення встановленого computeUnitPrice за годину/добу | Відстежуйте тренд для виявлення аномалій |
Джерела даних для моніторингу
- Власні логи. Фіксуйте для кожної транзакції: час відправки, computeUnitPrice, computeUnitLimit, час підтвердження, slot, підпис. Це базовий мінімум.
- Події транзакцій. Після підтвердження отримуйте детальну інформацію через
getTransactionз параметромmaxSupportedTransactionVersion: 0. Полеmeta.computeUnitsConsumedпокаже фактичне споживання. - Зовнішні дашборди. Публічні інструменти (наприклад, Solana Explorer, інші моніторингові платформи) показують загальну конгестію мережі, але не дають переліку ваших транзакцій. Використовуйте їх для контексту, а не для точного моніторингу.
Діагностика типових проблем
Транзакції підтверджуються, але повільно (5+ слотів) при високому computeUnitPrice. Причина зазвичай не у priority fee. Перевірте: чи не перевищує ваша транзакція розмір 1232 байти; чи не використовуєте ви занадто багато великих акаунтів, що створює тиск на пам'ять лідера; чи не конкуруєте ви з MEV-ботами за доступ до тих самих акаунтів (у цьому випадку навіть дуже високий fee може не допомогти — потрібні інші транзакційні патерни).
Транзакції періодично відхиляються з помилкою TransactionExpiredBlockheightExceeded. Це означає, що lastValidBlockHeight транзакції досягнуто до її включення в блок. Збільште ttl (time-to-live) при формуванні транзакції або використовуйте durable nonce для транзакцій, що мають чекати довго.
Фактичний priority fee значно нижчий за максимальний. Це означає, що computeUnitLimit завищено відносно фактичного споживання. Зменшіть ліміт на основі даних computeUnitsConsumed з останніх транзакцій. Це знизить ефективний пріоритет, але якщо конгестія низька, це прийнятний компроміс за економію коштів.
Автоматизована перевірка
Реалізуйте періодичну перевірку (наприклад, кожні 5 хвилин), що порівнює ваш поточний computeUnitPrice з актуальним 75-м перцентилем. Якщо різниця перевищує 50% у будь-який бік — логуйте попередження. Це дозволить виявити проблеми з кешуванням RPC-відповідей або збої в логіці динамічного регулювання до того, як вони вплинуть на користувачів.