Перейти к основному содержанию
Эта страница переведена с помощью ИИ. За эталон принимается английская версия.Открыть английскую версию →
Матрица поддержки: CPMM полностью поддерживает Token-2022, включая миинты с transfer-fee. CLMM поддерживает Token-2022 с transfer-fee через явные аккаунты SwapV2. AMM v4 вообще не поддерживает Token-2022. LaunchLab не поддерживает Token-2022 для базового мипта (создаёт классические SPL-миинты). Farm v6 поддерживает Token-2022 на обоих минтах: staking и reward.

Что такое transfer-fee

Token-2022 — это вторая программа SPL Token (TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DATokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb). Среди её расширений расширение transfer-fee вычитает комиссию из каждого TransferChecked на минте. Комиссия идёт на адрес, назначенный администратором минта, и может быть обновлена администратором (в пределах ограничений). Минт с transfer-fee имеет два релевантных параметра:
  • transfer_fee_basis_points — ставка (например, 100 = 1%).
  • maximum_fee — абсолютный лимит за один перевод (чтобы киты, переводящие огромные суммы, не платили неограниченные комиссии).
Минт может иметь две активные конфигурации transfer-fee одновременно: «новую» (действует сейчас) и «старую» (выводится). Это дизайн «epoch transition» — изменения transfer-fee вступают в силу на границе эпохи, чтобы не удивлять транзакции в полёте.

Почему это важно для свопов

Хранилища пула содержат реальные балансы. Когда пользователь вызывает своп Raydium:
  1. Пользователь отправляет amount_in в хранилище пула. Если входящий минт имеет transfer-fee, хранилище получит amount_in − fee_in, а не amount_in.
  2. Математика свопа операционирует полученной хранилищем суммой.
  3. Пул отправляет amount_out на ATA пользователя. Если исходящий минт имеет transfer-fee, пользователь получит amount_out − fee_out, а не amount_out.
Если программа свопа наивно использует сырой аргумент amount_in, проверка инварианта не пройдёт, потому что хранилище получило меньше, чем программа думает. И наоборот, если она вычисляет amount_out без вычитания исходящей комиссии, пользователь видит недостачу и винит программу. Raydium CPMM и CLMM (через SwapV2) разрешают это так:
  • До свопа: вычисляют in_after_fee = amount_in − transfer_fee_on(amount_in, in_mint) и используют in_after_fee в математике кривой.
  • После свопа: вычисляют out_gross = amount_out_from_curve, отправляют out_gross пользователю через TransferChecked, которую сама программа Token-2022 уменьшит на transfer-fee.
minAmountOut слипиджа пользователя проверяется против out_gross (того, что отправил пул), а не против того, что получил пользователь. Так работает каждый крупный DEX на Solana, и это важно, потому что:
  • Если пул проверял пост-комиссию, обновление комиссии между котировкой и исполнением привело бы к revert сделки.
  • Проверка пре-комиссии привязывает отказ к качеству самой котировки, а не к внеочередным изменениям пользовательских комиссий.
UI-ы должны вычитать ожидаемую transfer-fee Token-2022 при показе «Вы получите» пользователю.

Вычисление комиссии Token-2022

Программа SPL Token-2022 предоставляет детерминированный помощник. На Rust:
На TypeScript (через @solana/spl-token):

Скорректированные формулы свопа (CPMM, exact-input)

Пусть f_pool — ставка комиссии пула, f_in — ставка transfer-fee входящего минта, max_in — его максимальный лимит, f_out — ставка transfer-fee исходящего минта, max_out — его максимальный лимит.
Проверка слипиджа: amount_out_gross ≥ min_amount_out (а не user_receives ≥ min_amount_out). minAmountOut пользователя устанавливается SDK как expected_gross · (1 − slippage) — держите границу на стороне «отправленного», а не «полученного».

Скорректированные формулы (CPMM, exact-output)

SDK итерирует, чтобы найти amount_in такой, что user_receives = amount_out_exact:
Лимиты max_in / max_out делают вычисление нелинейным, потому что как только лимит достигнут, комиссия перестаёт расти. computeAmountIn / computeAmountOut SDK-а обрабатывают это итерацией, если наивная формула проталкивала бы за лимит.

Граничные случаи

Асимметричные комиссии (одна сторона имеет комиссию, другая — нет)

Частое явление на практике. Приведённые выше формулы это уже обрабатывают — если одна сторона имеет f_in = 0, релевантные члены исчезают. Специального случая в программе нет.

Обновления комиссии во время свопа

Если transfer-fee минта изменится между временем котировки и исполнением, своп либо приземлится с немного худшей экономикой (пользователь несёт разницу в пределах допуска слипиджа), либо вернётся (валовой выход упадёт ниже minAmountOut). Границы слипиджа это абсорбируют; дополнительная защита не требуется.

Максимальный лимит комиссии

Когда сделка достаточно велика, чтобы достичь maximum_fee, комиссия насыщается и дальнейший рост равен нулю. Это делает эффективную ставку асимптотической к нулю для очень крупных сделок, что может вызвать странные кривые цен на глубоко неликвидных рынках. computeAmountOut SDK-а это учитывает.

Расширение NonTransferable

Некоторые Token-2022 миинты используют расширение NonTransferable, которое отклоняет все вызовы Transfer кроме как к и от администратора минта. Такие миинты не могут вообще использоваться в пуле Raydium. CreatePool их отклоняет при инициализации.

Interest-bearing миинты

Token-2022 также поддерживает расширение InterestBearingConfig, которое делает балансы растущими со временем. Пулы Raydium читают сырые балансы хранилищ (которые игнорируют накопление процентов), поэтому в пуле с interest-bearing минтом LPs захватывают накопленные проценты как чистый подарок всякий раз, когда они redeem (баланс хранилища рос быстрее, чем представление LP-снабжения). Интеграторы должны рассматривать это как неважное, но задокументировать для LP-стороны.

Transfer hooks

Расширение TransferHook Token-2022 позволяет произвольный CPI на каждой передаче. Raydium CPMM это поддерживает — инструкция свопа пересылает аккаунты hook — но это добавляет CU overhead и требует, чтобы hook был хорошо себя вёл. CLMM SwapV2 также поддерживает hooks. AMM v4 не поддерживает Token-2022 вообще, так что вопрос не возникает.

Разобранный пример

Пул CPMM, x = 1_000_000 USDY, y = 1_000_000 USDC, комиссия пула 0.25%.
  • USDY имеет 1% transfer-fee, max_fee = 10_000 (0.01 USDY с 6 decimals).
  • USDC не имеет transfer-fee.
Пользователь меняет amount_in = 1_000 USDY на USDC (exact-input).
≈ 985.97 USDC. Нет исходящей transfer-fee, поэтому пользователь получает 985.97 USDC.

Указатели

Источники: