Каждая транзакция Solana устанавливает (явно или неявно) два параметра: лимит вычислительных единиц (максимум CU, которые может потребить tx; по умолчанию 200 000 × количество инструкций с ограничением по транзакции) и комиссию за приоритет в микро-ламортах за CU. Недостаточный размер любого параметра приводит к отказу транзакции — слишком низкий лимит CU вызывает ProgramFailedToComplete; слишком низкая комиссия за приоритет оставляет транзакцию неподтверждённой до истечения срока.
Два параметра
import { ComputeBudgetProgram } from "@solana/web3.js";
const tx = new Transaction()
.add(ComputeBudgetProgram.setComputeUnitLimit({ units: 250_000 }))
.add(ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 50_000 }))
.add(yourRaydiumSwapIx);
setComputeUnitLimit(units) — ограничивает вычисления; транзакция оплачивает не более units CU.
setComputeUnitPrice(microLamports) — ставка комиссии за приоритет в микро-ламортах за CU. Общая комиссия за приоритет = units × microLamports × 1e-6 ламортов.
Расчёт стоимости: лимит 250k CU при 50k микро-ламортов/CU стоит 250_000 × 50_000 / 1e6 = 12,500 ламортов ≈ 0,0000125 SOL ≈ $0,003 при SOL = $200. Комиссии за приоритет такого размера — едва заметны для большинства пользовательских свопов, но материальны для ботов, выполняющих 1000 tx/день.
Бенчмарки CU по инструкциям
Бенчмарки из журналов выполнения на mainnet, усреднённые по последним запускам. Цифры приблизительные (±15%); переизмеряйте для ваших конкретных потоков.
| Инструкция | SPL Token | Token-2022 (простой) | Token-2022 (трансфер с комиссией) |
|---|
| CPMM initialize_pool | 180 000 | 200 000 | — |
| CPMM swap_base_input | 140 000 | 180 000 | 200 000 |
| CPMM swap_base_output | 150 000 | 185 000 | 205 000 |
| CPMM deposit | 130 000 | 160 000 | 180 000 |
| CPMM withdraw | 120 000 | 150 000 | 170 000 |
| CLMM create_pool | 70 000 | 85 000 | — |
| CLMM open_position_v2 | 120 000 | 140 000 | 160 000 |
| CLMM increase_liquidity_v2 | 150 000 | 175 000 | 195 000 |
| CLMM decrease_liquidity_v2 | 140 000 | 165 000 | 185 000 |
| CLMM swap_v2 (0 пересечений тиков) | 170 000 | 205 000 | 225 000 |
| CLMM swap_v2 (1 пересечение тика) | 220 000 | 255 000 | 275 000 |
| CLMM swap_v2 (3 пересечения тиков) | 320 000 | 355 000 | 375 000 |
| CLMM collect_fee | 80 000 | 95 000 | 105 000 |
| AMM v4 swap_base_in | 140 000 | — | — |
| AMM v4 deposit | 120 000 | — | — |
| AMM v4 withdraw | 110 000 | — | — |
| Farm v6 create_farm | 70 000 | 85 000 | — |
| Farm v6 deposit (1 слот награды) | 130 000 | 155 000 | 175 000 |
| Farm v6 deposit (3 слота награды) | 220 000 | 255 000 | 275 000 |
| Farm v6 withdraw | совпадает с deposit | | |
| Farm v6 harvest | совпадает с deposit | | |
| Farm v3/v5 deposit | 100 000 | — | — |
| LaunchLab initialize | 100 000 | — | — |
| LaunchLab buy_exact_in | 140 000 | — | — |
| LaunchLab graduate | 250 000 | — | — |
Строка «пересечения тиков» для CLMM — это самая большая переменная CU. Если вы не знаете, сколько тиков пересечёт свап, планируйте наихудший случай — 8 пересечений — это жёсткий лимит (программа загружает максимум 8 массивов тиков).
Составные транзакции
Суммируйте отдельные бюджеты и добавьте:
- +1 500 CU за каждый фрейм CPI — фиксированные накладные расходы runtime для каждого перекрёстного вызова программы.
- +20 000 CU за создание ATA —
create_associated_token_account не бесплатна.
- +5 000 CU за
setComputeUnitLimit / setComputeUnitPrice каждая.
Пример: пользовательский свап, создающий выходной ATA и обёртывающий нативный SOL:
wrap_sol (create_ata + system transfer + sync_native) ≈ 30 000
CPMM swap_base_input (SPL) ≈ 140 000
close_account (unwrap) ≈ 5 000
ComputeBudget инструкции ≈ 10 000
────────────────────────────────────────────────────────
Итого ≈ 185 000 → бюджет 250 000
Запас: установите лимит CU примерно на 25% выше ожидаемого использования. Недооценка стоит всю транзакцию; переоценка только пропорционально повышает стоимость комиссии за приоритет (комиссия за приоритет — это units × microLamports, поэтому переоценка на ~25% стоит на 25% больше в приоритетной комиссии).
Оценка комиссии за приоритет
Локальный рынок комиссий Solana означает, что комиссии за приоритет — для каждого записываемого аккаунта. Транзакция, которая записывает на горячий аккаунт (популярное состояние пула), платит больше, чем транзакция, записывающая на холодный аккаунт. Глобальный уровень комиссий — неправильная метрика для свопов Raydium; вам нужны комиссии на конкретных пулах, которые вы трогаете.
Стратегия 1: оценитель RPC-провайдера
Каждый крупный RPC-провайдер публикует оценитель комиссий за приоритет, который запрашивает недавние комиссии на конкретных аккаунтах:
// Helius
const response = await fetch(`https://mainnet.helius-rpc.com/?api-key=${apiKey}`, {
method: "POST",
body: JSON.stringify({
jsonrpc: "2.0",
id: "fee-estimate",
method: "getPriorityFeeEstimate",
params: [{
accountKeys: [poolStatePubkey.toBase58()],
options: { priorityLevel: "High" },
}],
}),
});
const { result } = await response.json();
const microLamports = result.priorityFeeEstimate;
Уровни приоритета у большинства провайдеров: Min / Low / Medium / High / VeryHigh / UnsafeMax. Отображайте их на перцентили:
| Уровень | Перцентиль | Случай использования |
|---|
| Min | 25-й | Фоновый, неспешный трафик ботов |
| Low | 50-й | Обычные пользовательские свопы |
| Medium | 60-й | По умолчанию для UI кошельков |
| High | 75-й | Чувствительный по времени арбитраж |
| VeryHigh | 95-й | Ликвидации, последние шансы выхода |
Провайдеры: Helius (getPriorityFeeEstimate), Triton (getRecentPrioritizationFees со списком аккаунтов), QuickNode (аналогично).
Стратегия 2: прямой RPC-запрос
Используйте стандартный RPC getRecentPrioritizationFees:
const fees = await connection.getRecentPrioritizationFees({
lockedWritableAccounts: [poolStatePubkey],
});
// fees: Array<{ slot, prioritizationFee }>
// Последние N слотов; по умолчанию ~150 слотов.
const median = percentile(fees.map(f => f.prioritizationFee), 0.5);
Это ванильный метод RPC Solana; работает с любым провайдером. Минус: выборка мала (150 слотов ≈ 60 секунд) и шумна. Для более гладких оценок используйте агрегацию провайдера.
Стратегия 3: историческая самонастройка
Для ботов с постоянным потоком отслеживайте собственные скорости приземления и истечения:
целевой показатель за пул: 80% rate приземления за <30s
если current_land_rate < 80%: priorityFee += 10%
если current_land_rate > 95%: priorityFee -= 5%
Это самокорректируется быстрее, чем публичные оценители, и захватывает структуру за пулом, которую публичные оценители не всегда видят.
Обработка отказов из-за исчерпания CU
Симптом: транзакция падает с exceeded maximum number of instructions allowed (200000) или ProgramFailedToComplete.
Диагностика:
solana confirm <tx-sig> -v
# Ищите "consumed N of M compute units" и какая инструкция исчерпала.
Исправления:
- Увеличьте лимит CU. Если ваша транзакция использовала 195k из бюджета 200k, увеличьте до 300k.
- Разделите транзакцию. Если вы достигли лимита 1,4M за транзакцию, разбейте на две транзакции. Фарм
harvest then stake — классический случай для разделения при множественных наградах.
- Обрезьте аккаунты. Каждый дополнительный записываемый аккаунт добавляет ~2 000 CU. Удаление неиспользуемых аккаунтов помогает в маргинальных случаях.
- Используйте таблицы поиска. Поиск LUT — ~50 CU за разрешённый адрес, сэкономив 5 000 CU полной ссылки на аккаунт за запись.
Обработка застрявших транзакций
Симптом: транзакция отправлена, никогда не подтверждается, в итоге истекает с BlockhashNotFound.
Диагностика:
getSignatureStatuses([sig]) возвращает null → лидер её не видел.
- Возвращает
{ confirmationStatus: null } → лидер видел, но не включил.
Исправления:
- Увеличьте комиссию за приоритет. Переотправьте с 2× текущей комиссией.
- Перестройте со свежим блокхешем. Время жизни блокхеша ~60 секунд; сверх этого транзакция невалидна независимо от комиссий.
- Многорассылка по RPC. Некоторые RPC имеют лучшую связность с лидерами. Отправляйте параллельно 3–5 провайдерам.
- Переключитесь на Jito bundles. См.
integration-guides/routing-and-mev. Бандлы обходят публичные очереди пакетов.
Скелет логики повтора:
async function submitWithRetry(buildTx, maxAttempts = 5) {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const tx = await buildTx({
priorityFee: basePriorityFee * Math.pow(1.5, attempt),
blockhash: (await connection.getLatestBlockhash()).blockhash,
});
try {
const sig = await connection.sendRawTransaction(tx.serialize(), {
skipPreflight: attempt > 0, // пропустить после первой попытки, чтобы сэкономить задержку
});
const result = await connection.confirmTransaction(sig, "confirmed");
if (result.value.err) {
// Логическая ошибка; не повторяйте.
throw result.value.err;
}
return sig;
} catch (e) {
if (isExpiredError(e)) continue; // повтор
if (isRevertError(e)) throw e; // не повторяйте; детерминированный отказ
throw e;
}
}
throw new Error("submit: exhausted retries");
}
При перегрузке
Когда сеть перегружена (панели мониторинга Jupiter / Jito bundles показывают задолженность, задержка RPC растёт, темпы истечения tx растут), отрегулируйте:
| Параметр | Нормальные условия | Условия перегрузки |
|---|
| Лимит CU | +25% выше оценки | +25% выше оценки (без изменений) |
| Перцентиль комиссии за приоритет | 50-й | 75-й–95-й |
| Количество повторов | 3 | 5–7 |
| Откат повтора | 500ms | 1000ms |
| Использовать Jito bundles | Необязательно | Настоятельно рекомендуется |
| Обновление блокхеша при повторе | Да | Да, обязательно |
Мониторинг сигналов перегрузки:
- 75-й перцентиль комиссии за приоритет > 500k микро-ламортов: перегрузка.
- 50-й перцентиль чаевых Jito > 0,001 SOL: перегрузка.
- RPC ответ p99 > 2s: проблема RPC или перегрузка.
Бюджетирование комиссий для ботов
Торговый бот, работающий ~1000 tx/день, нуждается в бюджете комиссии за приоритет. Примерная оценка:
Среднее CU за транзакцию: ~250 000
50-й перцентиль комиссии: ~20 000 микро-ламортов/CU
Стоимость за транзакцию: 250_000 × 20_000 × 1e-6 = 5_000 ламортов = 5e-6 SOL
Дневная стоимость (1000 tx): 5e-3 SOL ≈ $1 при SOL = $200
Месячная стоимость: ~$30
Это минимум. При перегрузке умножьте на 5–10×. Планируйте ~$150–300/месяц в комиссиях за приоритет для бота с установившимся потоком.
Боты, которые должны приземлиться в конкретные слоты (ликвидации, арб), постоянно платят 95-й перцентиль и тратят ~10× больше. Чаевые Jito bundle доминируют в этом масштабе — часто $1000+/месяц — но альтернатива (быть обогнанным или истечь) хуже.
Ловушки
1. Забытый лимит CU
По умолчанию 200k CU × (инструкции в tx). Свап с одной инструкцией по умолчанию 200k; этого достаточно для CPMM на SPL Token, но не для CLMM с пересечениями тиков или чего-либо Token-2022. Всегда устанавливайте явно.
2. Комиссия за приоритет на неправильном аккаунте
Если вы оцениваете комиссию за приоритет для минта токена, но горячий аккаунт — состояние пула, ваша оценка слишком низкая. Состояние пула — правильный записываемый аккаунт для нацеливания в Raydium.
3. Комиссии масштабируются с лимитом CU
total_priority_fee = units × microLamports. Повышение units с 200k до 1M при 50k микро-ламортов/CU умножает комиссию за приоритет на 5×. Не переполняйте CU просто на всякий случай; измеряйте.
4. Версия транзакции по умолчанию
Legacy транзакции имеют меньше лимитов аккаунтов; V0 транзакции с таблицами поиска адресов открывают большие маршруты. SDK использует V0 по умолчанию в txVersion: TxVersion.V0. Не понижайте до legacy, если не нужна совместимость с кошельком.
5. skipPreflight скрывает ошибки CU
skipPreflight: true отправляет транзацию без локальной симуляции. Вы экономите ~100ms, но теряете раннюю обратную связь об исчерпании CU. Используйте только при повторах, а не при первой попытке.
Ссылки
Источники: