Перейти к основному содержанию

Native DEX

✅ Готово к разработке

Эта спецификация контракта прошла аудит бизнес-логики, техническую проверку, проверку кросс-программной интеграции (OT + RWT + YD), верификацию архитектуры комиссий и анализ потоков crank-операций. Разработчик может реализовать контракт по этому документу.
Специализированный AMM для торговли токенами OT и RWT. Два типа пулов: постоянное произведение (StandardCurve) для пар OT/RWT и сторонних, и односторонняя концентрированная ликвидность на основе бинов (Monotonic Ladder) для мастер-пулов RWT/USDC и RWT/USDY. Создание пулов ограничено вайтлистом. Swap-комиссии делятся между LP-холдерами (учитываются per-side через Q64.64 аккумуляторы на PoolState, забираются через claim_lp_fees) и Areal Finance (протокольная комиссия в RWT), взимаются поверх свопа для сохранения капитализации пула. OT-пары взимают дополнительные 0,5% в казначейство OT-проекта. Мастер-пулы маршрутизируют USDC→RWT сделки в rwt_engine::mint_rwt, когда DEX ask неконкурентен — на mint-пути DEX-комиссия не взимается.
Обновляемый контракт. Полномочия на обновление программы = Team Multisig (Squads). Отделены от authority конфигурации и authority паузы.

Ключевые концепции

23 инструкции

Конфигурация, создание пулов, добавление/удаление/zap ликвидности, своп (с ветвлением маршрутизации в mint), grow, compress, компаундинг, nexus (инициализация, депозит, вывод прибыли, своп, добавление/удаление LP, обновление менеджера), пауза, authority

6 аккаунтов состояния

DexConfig, PoolState, PoolCreators, LpPosition, BinArray, LiquidityNexus

6 PDA Seeds

dex_config, pool_creators, pool, lp, bins, liquidity_nexus

Два типа пулов

StandardCurve

Постоянное произведение: x × y = k
  • Простая, проверенная математика AMM
  • Непрерывное ценообразование: price = reserve_b / reserve_a
  • LP-доли: sqrt(a × b) при первом добавлении, пропорционально после
  • Используется для: пар OT/RWT, сторонних пар

Monotonic Ladder

Односторонняя концентрированная ликвидность (только bid)
  • 1000 бинов на пул, log-scale, bin_step_bps = 10 (0,1%) по умолчанию → покрывает рост NAV ~2,7× от начального якоря
  • Три зоны: permanent tail (неподвижный USDC на initial NAV − 1%), active bid wall (geometric density r = 0.85 вокруг NAV), organic ask (RWT, накопленный из продаж пользователей; не пред-финансируется)
  • Nexus LP вносит только USDC — без предварительного депозита RWT
  • Swap(USDC→RWT) маршрутизируется в rwt_engine::mint_rwt, когда organic ask исчерпан или DEX-цена > NAV × 1.005 (mint становится дешевле)
  • grow_liquidity (Rebalancer) расширяет bid wall вправо по мере роста NAV — никогда не двигает permanent tail, никогда не трогает organic ask
  • compress_liquidity (Rebalancer) пересчитывает density при writedown — замороженные ask RWT выше нового NAV сохраняются для восстановления
  • Используется исключительно для: мастер-пулов RWT/USDY и RWT/USDC
Почему односторонний? У RWT монотонно не убывающий NAV и permissionless-инструкция mint_rwt, которая производит RWT по детерминированной потолочной цене NAV × 1.01. Это делает любой пред-депонированный RWT ask-сайд выше NAV × 1.005 экономически мёртвым капиталом — покупатели всегда предпочтут mint. Monotonic Ladder помещает 100% Nexus LP в дефицитный ресурс (USDC bid depth) и делегирует ask-сторону самому vault через маршрутизацию в mint.

Архитектура комиссий

1

Базовая комиссия

Применяется к каждому свопу. По умолчанию: 50 bps (0,5%). Максимум: 1 000 bps (10%). Устанавливается для каждого пула при создании, копируется из DexConfig.
2

Распределение комиссии

  • LP-комиссия (по умолчанию 50% от базовой) → начисляется на каждой стороне через Q64.64 cumulative-per-share аккумуляторы на PoolState (cumulative_fees_per_share_a / _b). Комиссии остаются внутри резервных vault’ов пула (vault_a / vault_b); pool_state.reserve_a / _b не дилюются. LP-холдеры забирают вознаграждения через claim_lp_fees и получают обе стороны токенов — мгновенно, без vesting.
  • Протокольная комиссия (по умолчанию 50% от базовой) → всегда в RWT, переводится на areal_fee_destination (RWT ATA Areal Finance).
3

Комиссии поверх свопа (без размывания пула)

Все комиссии взимаются поверх суммы свопа в RWT. Резервы пула никогда не уменьшаются на комиссии — общая капитализация пула остаётся неизменной. Если пользователь продаёт RWT: пользователь платит amount_in + fees (дополнительные RWT сверху). Если пользователь покупает RWT: пользователь получает amount_out полностью, комиссии берутся отдельно из валового выхода RWT перед доставкой.
4

Всегда в RWT

Каждый пул образует пару с RWT (например, OT/RWT, RWT/USDC). Все комиссии (LP, протокольная, казначейство OT) всегда берутся в RWT. Не нужно указание токена комиссии, логика конвертации не требуется.
5

LP-комиссия → per-side аккумулятор (мгновенный вывод)

LP-комиссии остаются внутри резервных vault’ов пула (vault_a и vault_b). Контракт ведёт два per-side Q64.64 cumulative-per-share аккумулятора на PoolState (cumulative_fees_per_share_a / _b); каждая LpPosition снимает их снимок при каждом взаимодействии (fees_claimed_per_share_a / _b). LP-холдеры забирают вознаграждения в любой момент через claim_lp_fees и получают обе стороны пула — без вестинга, без merkle proof, без офф-чейн сервера. Доступная сумма по стороне: (cumulative_fees_per_share_<side> − fees_claimed_per_share_<side>) × shares >> 64. Текущая реализация свопа всегда начисляет LP-комиссию на RWT-стороне, поэтому для RWT-парного пула продвигается только RWT-аккумулятор; dual-side раскладка — forward-compat для не-RWT пар.
6

Комиссия казначейства OT (только для пар с OT)

Пулы с токеном OT взимают дополнительные 50 bps (0,5%) на каждом свопе — отправляются целиком в RWT на Treasury RWT ATA проекта OT (управляется Futarchy). Итоговая эффективная комиссия для пар с OT: 100 bps (1%). Пулы без OT (например, RWT/USDC) не затронуты — стандартные 50 bps. Комиссия казначейства OT рассчитывается от той же валовой суммы, что и базовая комиссия (без компаундинга). Хранится как программная константа OT_TREASURY_FEE_BPS = 50 — изменяется только через обновление программы.
7

Пропуск комиссии на mint-пути (только мастер-пулы)

Когда мастер-пул Monotonic Ladder маршрутизирует USDC→RWT в rwt_engine::mint_rwt (organic ask исчерпан или best ask price > NAV × 1.005), DEX-комиссия (LP и протокольная) не взимается. 1% mint fee — 0,5% в RWT vault (NAV accrual) + 0,5% в Areal DAO — уже выполняет роль комиссии, а LP не предоставляли тот RWT, который получает пользователь. Двойное налогообложение одной сделки недопустимо. Инструкция свопа определяет маршрут и ветвит fee-логику соответственно.

Шесть ролей

🏛️ Authority

Team Multisig (после начальной настройки)
  • update_dex_config — комиссии, адреса назначения, rebalancer
  • update_pool_creators — управление вайтлистом
  • propose_authority_transfer

🛑 Pause Authority

Team Multisig (Squads)
  • pause_pool / unpause_pool
Неизменяемый — устанавливается при инициализации, изменяется только через обновление программы.

⚖️ Rebalancer

Кошелёк Pool Rebalancer (выделенная ключевая пара бота)
  • grow_liquidity — расширение bid wall вправо при росте NAV
  • compress_liquidity — перенос центра density при writedown
Устанавливается при инициализации, изменяется authority через update_dex_config.

💧 Nexus Manager

Кошелёк бота (LP-бот Areal Finance)
  • nexus_swap / nexus_add_liquidity / nexus_remove_liquidity
Управляет LP-позициями Areal Finance. Изменяется authority через update_nexus_manager.

🏗️ Pool Creators

Кошельки из вайтлиста (макс. 10)
  • create_pool / create_concentrated_pool
Закрытая платформа — только одобренные создатели. Управляется authority.

🌐 Без ограничений

Любой кошелёк
  • swap — торговля токенами
  • add_liquidity / zap_liquidity / remove_liquidity
  • compound_yield — автокомпаундинг RWT

Инструкции

Инициализация

Создание глобальной конфигурации DEX и вайтлиста создателей пулов. Вызывается один раз.Параметры:
ParameterTypeDescription
areal_fee_destinationPubkeyRWT ATA Areal Finance — получает протокольные комиссии в RWT (статический, неизменяемый)
pause_authorityPubkeyПодписант экстренной паузы (Team Multisig, неизменяемый)
rebalancerPubkeyКошелёк Pool Rebalancer — может вызывать grow_liquidity и compress_liquidity
Вызывающий: Deployer (однократно)Аккаунты:
  • authority (signer, mut) — deployer, оплачивает создание
  • dex_config (init) — PDA seed: ["dex_config"]
  • pool_creators (init) — PDA seed: ["pool_creators"]
  • system_program
Создаёт:
  • DexConfig PDA — глобальный синглтон конфигурации
  • PoolCreators PDA — вайтлист (deployer автоматически добавляется первым создателем)
Начальное состояние:
  • base_fee_bps = 50 (0,5%)
  • lp_fee_share_bps = 5,000 (50% комиссии для LP)
  • is_active = true

Создание пулов

Создание пула StandardCurve (постоянное произведение) для пары токенов. Без параметров — конфигурация пула (комиссия, тип) определяется из DexConfig и аккаунтов.Вызывающий: Создатель из вайтлистаАккаунты:
  • creator (signer, mut) — должен быть в вайтлисте pool_creators, оплачивает создание аккаунтов
  • dex_config — проверяет is_active, предоставляет base_fee_bps и lp_fee_share_bps
  • pool_creators — проверяет, что creator в вайтлисте
  • pool_state (init) — PDA seed: ["pool", token_a_mint, token_b_mint]
  • token_a_mint — ограничение: key < token_b_mint.key (каноничный порядок)
  • token_b_mint — ограничение: key > token_a_mint.key
  • vault_a (init) — SPL-аккаунт токена для A, authority = pool_state PDA (keypair, не ATA)
  • vault_b (init) — SPL-аккаунт токена для B, authority = pool_state PDA (keypair, не ATA)
  • ot_treasury (опционально) — OT Treasury PDA: ["ot_treasury", ot_mint]. Обязателен при создании пары с OT. Должен принадлежать OT_PROGRAM_ID.
  • ot_treasury_rwt_ata (опционально) — RWT ATA, принадлежащий PDA ot_treasury. Обязателен, когда предоставлен ot_treasury.
  • token_program, system_program
Валидация:
  • token_a_mint ≠ token_b_mint
  • Один из token_a_mint или token_b_mint должен быть RWT_MINT (все пулы образуют пару с RWT — необходимо для протокольной комиссии в RWT)
  • token_a_mint < token_b_mint (лексикографический порядок — каноничная деривация PDA, предотвращает дублирование пулов с обратным порядком)
  • Создатель должен быть в вайтлисте
  • Если предоставлен ot_treasury: проверка деривации PDA ["ot_treasury", ot_mint], где ot_mint — минт, не являющийся RWT. Проверка, что владелец аккаунта — OT_PROGRAM_ID. Проверка, что ot_treasury_rwt_ata является ассоциированным токен-адресом для (ot_treasury, RWT_MINT). Ошибка InvalidOtTreasuryDestination при невыполнении любой проверки.
Начальное состояние: Reserves = 0, total_lp_shares = 0, fee_bps скопирован из DexConfig. ot_treasury_fee_destination = ot_treasury_rwt_ata.key() если предоставлены аккаунты OT treasury, иначе None.
fee_bps пула копируется из DexConfig при создании и неизменяем после этого. Изменение base_fee_bps в DexConfig влияет только на будущие пулы. Для изменения комиссии существующего пула требуется обновление программы. Аналогично, ot_treasury_fee_destination устанавливается при создании и неизменяем.
Создание мастер-пула Monotonic Ladder с BinArray на 1000 слотов. Используется исключительно для RWT/USDC и RWT/USDY.
ParameterTypeDescription
bin_step_bpsu16Логарифмический шаг цены между бинами (по умолчанию: 10 = 0,1%)
initial_active_bini32Начальный ID активного бина — якорит initial NAV в log-scale
permanent_tail_offset_bpsi32Позиция permanent tail ниже initial_active_bin. По умолчанию: 100 (= NAV − 1%). Иммутабельно после init.
Вызывающий: Создатель из вайтлистаАккаунты: Те же, что и create_pool, плюс:
  • bin_array (init) — PDA seed: ["bins", pool_state]. Размер = 1000 бинов × 24 байта + overhead ≈ 32 КБ, rent ~0.22 SOL.
Валидация:
  • bin_step_bps > 0
  • Один из token_a_mint или token_b_mint должен быть RWT_MINT
  • Не-RWT mint должен быть USDC_MINT или USDY_MINT (паттерн Monotonic Ladder определён только для этих двух пар)
  • token_a_mint < token_b_mint (каноничный порядок)
  • Создатель должен быть в вайтлисте
  • permanent_tail_offset_bps ≥ 30 (должен быть минимум 0,3% ниже initial NAV, чтобы active zone и tail не пересекались)
  • Валидация OT treasury: должна отсутствовать (мастер-пулы не являются OT-парами)
Начальное состояние:
  • 1000 пустых бинов. active_bin_id = initial_active_bin.
  • left_anchor_bin = initial_active_bin − permanent_tail_offset_bps / bin_step_bps — иммутабельно, отмечает верх permanent tail.
  • permanent_tail_floor_bin = left_anchor_bin − 70 — т.е. хвост занимает 70 бинов (~0.7% при шаге 0.1%) непосредственно ниже left anchor. Также иммутабельно.
  • Ликвидность при init не разворачивается — Nexus засевает через nexus_add_liquidity после создания пула.
Monotonic Ladder не является универсальным concentrated-пулом. Его геометрия, ветвление комиссий и семантика ребалансировки специализированы под пары RWT/USDx. Пулы OT/RWT используют StandardCurve.

Ликвидность

Единый LP-интерфейс для пулов StandardCurve. Пользователи взаимодействуют через add_liquidity, zap_liquidity и remove_liquidity в парах OT/RWT и сторонних парах. LP получают пропорциональные доли и зарабатывают через per-side аккумулятор на PoolState с клеймом через claim_lp_fees.Мастер-пулы (Monotonic Ladder) засеиваются исключительно через Nexus. Пользовательский add_liquidity и zap_liquidity на мастер-пулах RWT/USDC и RWT/USDY падает с MasterPoolUserLpDisabled. Обоснование: Ladder — это односторонняя USDC-структура с протокольно-управляемой геометрией (permanent tail, веса active zone, маршрутизация в mint). Приём произвольного пользовательского LP нарушил бы инварианты density и создал бы ask-side позиции, подрывающие маршрутизацию в mint. Пользователи, желающие доходность на потоке мастер-пула, могут держать RWT напрямую (он растёт через NAV) и арбитражить при лаге рыночной цены.Ask-side RWT, заходящий в мастер-пулы через продажи пользователей, — единственный путь накопления RWT в мастер-пул-бинах. Он не минтит LP-доли — это органический инвентарь, принадлежащий пулу для потребления будущими покупателями через bin walk.
Добавление ликвидности в любой пул (StandardCurve или Concentrated). Получение LP-долей пропорционально депозиту. Для пулов Concentrated токены поступают в хранилища, а Rebalancer распределяет их по бинам отдельно.
ParameterTypeDescription
amount_au64Количество токена A
amount_bu64Количество токена B
Вызывающий: Без ограниченийАккаунты:
  • provider (signer) — владеет токен-аккаунтами
  • payer (signer, mut) — оплачивает ренту за инициализацию LpPosition
  • pool_state (mut) — обновляет резервы, lp_shares
  • lp_position (init_if_needed) — PDA seed: ["lp", pool_state, provider]
  • provider_token_a (mut) — ограничение: owner == provider, mint == pool.token_a_mint
  • provider_token_b (mut) — ограничение: owner == provider, mint == pool.token_b_mint
  • vault_a (mut), vault_b (mut) — хранилища пула
  • bin_array (mut, опционально) — обязателен для пулов Concentrated (для распределения по бинам)
  • token_program, system_program
Валидация:
  • amount_a > 0 && amount_b > 0 (оба обязательны; для одного токена используйте zap_liquidity)
  • Пул должен быть активен
Математика (одинакова для обоих типов пулов):
  • Первое добавление: shares = sqrt(amount_a × amount_b), должно быть ≥ MIN_LIQUIDITY (1 000). Защита от переполнения: amount_a * amount_b должно помещаться в u128.
  • Последующие: shares = min(amount_a × total_shares / reserve_a, amount_b × total_shares / reserve_b). min() означает, что избыточные токены одной стороны не вносятся — используется только пропорциональная сумма. Неиспользованные токены остаются в кошельке пользователя.
Несбалансированные депозиты теряют стоимость. Если пользователь вносит в соотношении, отличном от соотношения пула, он фактически получает доли на основе МЕНЬШЕЙ стороны. Используйте zap_liquidity для несбалансированных сумм — он автоматически свопнет для соответствия соотношению пула. Офф-чейн UI должен рассчитать оптимальные суммы перед вызовом add_liquidity.
Эффект: Перевод токенов в хранилища, минтинг LP-долей, обновление резервов. Эмитирует LiquidityAdded.
add_liquidity доступен только на пулах StandardCurve. На мастер-пулах Monotonic Ladder (RWT/USDC, RWT/USDY) пользовательский add_liquidity и zap_liquidity падает с MasterPoolUserLpDisabled. Рост bid-стороны мастер-пулов происходит исключительно через grow_liquidity, финансируемый из аккумулятора Nexus. Обоснование — в секции Monotonic Ladder в блоке Два типа пулов.
Добавление ликвидности с любым соотношением токенов — включая один токен. Контракт автоматически свопает для соответствия соотношению пула, затем добавляет обе стороны. Работает для пулов StandardCurve и Concentrated. Атомарно — нет риска изменения цены между свопом и добавлением.
ParameterTypeDescription
amount_au64Количество токена A (может быть 0)
amount_bu64Количество токена B (может быть 0)
min_sharesu128Минимум LP-долей для получения (защита от проскальзывания)
Вызывающий: Без ограниченийАккаунты:
  • provider (signer) — владеет токен-аккаунтами
  • payer (signer, mut) — оплачивает ренту за инициализацию LpPosition
  • pool_state (mut) — должен быть активен
  • dex_config — для расчёта комиссий
  • lp_position (init_if_needed) — PDA seed: ["lp", pool_state, provider]
  • provider_token_a (mut) — ограничение: owner == provider, mint == pool.token_a_mint
  • provider_token_b (mut) — ограничение: owner == provider, mint == pool.token_b_mint
  • vault_a (mut), vault_b (mut) — хранилища пула
  • areal_fee_account (mut) — получает протокольную комиссию от внутреннего свопа
  • ot_treasury_fee_account (mut, опционально) — получает комиссию казначейства OT от внутреннего свопа (обязателен для пар с OT)
  • bin_array (mut, опционально) — обязателен для пулов Concentrated
  • token_program, system_program
Валидация:
  • amount_a > 0 || amount_b > 0 (минимум один токен)
  • Пул должен быть активен
Логика:
  1. Чтение текущего соотношения пула. Если пул пуст (reserves = 0): пропуск свопа, обработка как обычный add_liquidity с обеими суммами как есть. Иначе: target_ratio = reserve_a / reserve_b
  2. Расчёт количества для свопа для соответствия соотношению:
    If amount_a / amount_b > target_ratio:
      // Too much A — swap excess A → B
      excess_a = amount_a - (amount_b * reserve_a / reserve_b)
      swap_amount = excess_a / 2  (swap half of excess)
      Internal swap: A → B (same fee math as regular swap)
    Else:
      // Too much B — swap excess B → A
      excess_b = amount_b - (amount_a * reserve_b / reserve_a)
      swap_amount = excess_b / 2
      Internal swap: B → A
    
  3. После внутреннего свопа: соотношение пула изменилось, пересчёт оптимальных сумм для добавления
  4. Добавление ликвидности сбалансированными суммами (та же математика, что и add_liquidity)
  5. shares ≥ min_shares (проверка проскальзывания)
  6. Эмитирует ZapLiquidityExecuted
Zap одним токеном: Если amount_a = 0, весь amount_b делится — половина свопается в токен A, половина остаётся как токен B. Аналогично в обратную сторону. Это простейший UX: «внесите USDC, получите LP-доли.»
Внутренний своп несёт те же комиссии, что и обычный своп (LP-комиссия → инкремент cumulative_fees_per_share_<side>, протокольная комиссия → Areal Finance, комиссия казначейства OT → OT Treasury для пар с OT). Это сделано намеренно — zap не должен быть лазейкой без комиссий.
Удаление ликвидности из любого пула. Сжигание LP-долей, получение пропорциональных токенов из общих резервов.
ParameterTypeDescription
shares_to_burnu128LP-доли для погашения
Вызывающий: LP-провайдер (должен владеть позицией)Аккаунты:
  • provider (signer) — должен соответствовать lp_position.owner
  • pool_state (mut)
  • lp_position (mut) — ограничение: owner == provider, pool == pool_state
  • provider_token_a (mut), provider_token_b (mut)
  • vault_a (mut), vault_b (mut)
  • token_program
Математика (одинакова для обоих типов пулов):
amount_a = shares_to_burn * reserve_a / total_lp_shares
amount_b = shares_to_burn * reserve_b / total_lp_shares
Валидация:
  • shares_to_burn ≤ lp_position.shares
  • Работает даже когда пул на паузе (LP всегда может выйти)
Эффект: PDA пула подписывает переводы из хранилищ. Если lp_position.shares == 0 после сжигания, аккаунт LpPosition закрывается, рента возвращается провайдеру. Эмитирует LiquidityRemoved.
Расширение active bid wall вправо по мере роста NAV. Только Rebalancer. Работает на мастер-пулах Monotonic Ladder. Забирает свежий USDC из аккумулятора Nexus и перераспределяет существующий USDC в active zone так, чтобы пик geometric density стоял на новом NAV. Permanent tail никогда не трогается. Organic ask (RWT выше active_bin) никогда не трогается.
ParameterTypeDescription
new_nav_bini32Целевой NAV bin ID, вычисленный офф-чейн Rebalancer: new_nav_bin = log(nav_book_value) / log(1 + bin_step_bps/10000)
active_zone_widthu16Количество бинов в active bid wall (по умолчанию: 40; ~4% ценового диапазона при шаге 0.1%). Расположен справа-вниз от new_nav_bin, через extended bid
Вызывающий: Только Rebalancer (должен соответствовать dex_config.rebalancer)Аккаунты:
  • rebalancer (signer)
  • dex_config — валидирует rebalancer
  • pool_state (mut) — должен быть типа Monotonic Ladder
  • bin_array (mut)
  • liquidity_nexus (mut) — источник свежего USDC
  • nexus_usdc_ata (mut) — USDC-аккаунт Nexus (debited)
  • pool_vault_b (mut) — USDC-хранилище пула (credited)
  • token_program
Валидация:
  • Пул должен быть типа Monotonic Ladder
  • new_nav_bin > pool_state.last_rebalance_nav_bin (ребалансировка роста — строго вправо). Для движения влево — compress_liquidity.
  • new_nav_bin − pool_state.left_anchor_bin ≤ MAX_BINS − 10 (буфер на правом краю BinArray — предотвращает overflow)
  • Вычисленная нижняя граница active zone new_nav_bin − active_zone_width/2 ≥ left_anchor_bin + 1 (не должна пересекаться с permanent tail)
  • pool_state.is_active
Логика (асимметрично: рост вправо, перераспределение существующего):
  1. Определение диапазонов:
    tail_lower    = pool_state.permanent_tail_floor_bin
    tail_upper    = pool_state.left_anchor_bin
    old_active_lo = pool_state.active_zone_lower
    new_active_lo = new_nav_bin − active_zone_width / 2
    new_active_up = new_nav_bin                         // active zone заканчивается на NAV; выше NAV — organic ask
    
  2. Вычисление целевых density weights (geometric, r = GEOMETRIC_R_BPS / 10000, по умолчанию 8500 = 0.85):
    for bin in [new_active_lo .. new_active_up]:
      d = new_nav_bin − bin                  // расстояние до пика (0 на NAV, больше ниже)
      weight(bin) = r^d
    total_weight = sum(weight(bin))
    
  3. Расчёт требуемого USDC:
    current_active_usdc = sum(bin.liquidity_b for bin in [old_active_lo .. new_nav_bin])
    nexus_available     = nexus_usdc_ata.balance
    // Цель: geometric-weighted капитал в новой active zone, масштабированный фактором роста
    target_total_usdc   = current_active_usdc * GROWTH_SCALING + nexus_available
    
  4. Извлечение из Nexus: перевод nexus_available USDC из nexus_usdc_atapool_vault_b. Аккумулятор Nexus обнуляется (остаток сохраняется для следующего цикла).
  5. Перераспределение по новой active zone:
    for bin in [new_active_lo .. new_active_up]:
      target_bin_usdc = target_total_usdc * weight(bin) / total_weight
    
    // Diff rebalance: перемещает USDC из over-funded в under-funded бины внутри active zone.
    // Бины в [old_active_lo .. new_active_lo − 1] НЕ сливаются — становятся extended bid.
    
  6. Permanent tail: [tail_lower .. tail_upper] не трогается. Никогда не ребалансируется.
  7. Organic ask: [new_nav_bin + 1 .. MAX_BINS] не трогается. Содержит RWT от прошлых продаж пользователей, если были.
  8. Обновление state: pool_state.last_rebalance_nav_bin = new_nav_bin, pool_state.active_zone_lower = new_active_lo, active_bin_id grow_liquidity НЕ меняет (только свопы двигают его).
Форма ladder после нескольких growth:
permanent tail       extended bid         active bid wall    organic ask
▁▁▁▁                 ▂▂▃▃▃▄▄▄▄▄          ▅▆▇█              ▁░░                    (время t0)
▁▁▁▁                 ▂▂▃▃▃▄▄▄▄▄▄▄▄▄▄    ▅▆▇█              ░░                     (после grow в t1)
▁▁▁▁                 ▂▂▃▃▃▄▄▄▄▄▄▄▄▄▄▄▄▄ ▅▆▇█              ░                      (после grow в t2)
└ NAV-1% floor       └ старые active zones └ текущий NAV   └ RWT (продажи)
Эффект: USDC входит в пул через Nexus. Капитализация пула строго растёт. Эмитирует LiquidityGrown.
Переносит центр active bid wall на более низкий NAV после governance-writedown (rwt_engine::adjust_capital). Только Rebalancer. Редкий случай. Без притока токенов — чисто перераспределение весов внутри существующего капитала.
ParameterTypeDescription
new_nav_bini32Более низкий целевой NAV bin ID
active_zone_widthu16Та же семантика, что и у grow_liquidity
Вызывающий: Только RebalancerАккаунты:
  • rebalancer (signer), dex_config, pool_state (mut), bin_array (mut)
Валидация:
  • Пул должен быть типа Monotonic Ladder
  • new_nav_bin < pool_state.last_rebalance_nav_bin (только влево; для движения вправо — grow_liquidity)
  • new_nav_bin > pool_state.left_anchor_bin + 10 (новый NAV должен оставаться выше permanent tail)
Логика:
  1. Определение новой active zone: [new_nav_bin − active_zone_width/2 .. new_nav_bin].
  2. Поглощение бывшей active zone в extended bid: бины, которые были в active zone, но теперь сидят выше new_nav_bin (они держали USDC на пике density), сливаются с extended bid — сразу становятся естественной stress-buffer глубиной.
  3. Пересчёт geometric density weights вокруг new_nav_bin, используя только существующий USDC-капитал в новой active zone (без забора из Nexus на compress).
  4. Перераспределение USDC внутри новой active zone по весам. Бывшие extended bid бины далеко ниже нового NAV остаются как есть.
  5. Organic ask НЕ трогается. RWT, который был в ask-бинах выше старого NAV, но теперь выше ещё более низкого нового NAV, остаётся там как замороженная ask-стенка — если NAV восстановится через будущую доходность, эти позиции снова станут productive без ребалансировки.
  6. Permanent tail НЕ трогается.
  7. Обновление state: pool_state.last_rebalance_nav_bin = new_nav_bin, pool_state.active_zone_lower = new_active_lo.
Сохранение капитала: общий USDC в pool vault не меняется. Общий RWT в pool vault не меняется. Меняется только распределение весов внутри active zone.Эффект: Эмитирует LiquidityCompressed.

Своп

Своп токенов через любой пул. Контракт автоматически использует правильную математику в зависимости от типа пула: постоянное произведение для StandardCurve, обход бинов для Concentrated.
ParameterTypeDescription
amount_inu64Количество входного токена
min_amount_outu64Минимальный выход (защита от проскальзывания)
a_to_bboolНаправление свопа
Вызывающий: Без ограниченийАккаунты:
  • user (signer)
  • pool_state (mut) — должен быть активен
  • dex_config — должен быть активен
  • bin_array (mut, опционально) — обязателен только для пулов Concentrated
  • user_token_in (mut), user_token_out (mut)
  • vault_in (mut), vault_out (mut)
  • areal_fee_account (mut) — получает протокольную комиссию
  • ot_treasury_fee_account (mut, опционально) — получает комиссию казначейства OT. Обязателен, если pool_state.ot_treasury_fee_destination равен Some. Должен соответствовать сохранённому адресу.
  • token_program
Математика комиссий (оба типа пулов). Все комиссии в RWT, взимаются поверх:Если вход = RWT (пользователь продаёт RWT):
// Fees calculated on swap amount
fee_total = amount_in * fee_bps / 10,000
fee_lp = fee_total * lp_fee_share_bps / 10,000
fee_protocol = fee_total - fee_lp

// OT Treasury fee (only if ot_treasury_fee_destination is Some)
ot_treasury_fee = amount_in * OT_TREASURY_FEE_BPS / 10,000  // 50 bps on gross amount

// User pays fees ON TOP — total debit from user wallet:
user_total_debit = amount_in + fee_total + ot_treasury_fee
// Full amount_in goes into pool (no fee deduction from reserves)
amount_out = constant_product(amount_in)
Если вход = OT (пользователь покупает RWT):
amount_out_gross = constant_product(amount_in)

// Fees calculated on gross RWT output
fee_total = amount_out_gross * fee_bps / 10,000
fee_lp = fee_total * lp_fee_share_bps / 10,000
fee_protocol = fee_total - fee_lp

// OT Treasury fee (only if ot_treasury_fee_destination is Some)
ot_treasury_fee = amount_out_gross * OT_TREASURY_FEE_BPS / 10,000  // 50 bps on gross amount

// Fees deducted from gross output — user receives net:
amount_out = amount_out_gross - fee_total - ot_treasury_fee
Адреса назначения комиссий (оба направления):
  • fee_lp → остаётся внутри резервного vault’а пула на стороне начисления (сейчас — всегда RWT-сторона); контракт инкрементирует pool_state.cumulative_fees_per_share_<side>, чтобы LP-холдеры могли позже забрать свою долю через claim_lp_fees
  • fee_protocol → RWT переводится на areal_fee_account (RWT ATA Areal Finance)
  • ot_treasury_fee → RWT переводится на ot_treasury_fee_account (только для пар с OT)
  • Для пулов без OT ot_treasury_fee = 0 (без дополнительной комиссии)
Обновление резервов после свопа (для программиста):
// Reserves stay intact — fees never touch pool liquidity
reserve_in  += amount_in   // full swap amount enters pool
reserve_out -= amount_out_gross  // gross amount leaves pool (before fee split)
// All fees are external to pool reserves
total_fees_accumulated += fee_total + ot_treasury_fee
Выход StandardCurve:
amount_out = reserve_out * net_input / (reserve_in + net_input)
Выход Concentrated (обход бинов):
remaining = net_input  (or amount_in if fee taken from output)
total_out = 0
current_bin = active_bin_id

while remaining > 0:
  bin = bin_array[current_bin]
  
  if a_to_b (selling RWT for USDC):
    available = bin.liquidity_b  (USDC in this bin)
    price = (1 + bin_step_bps/10000) ^ current_bin
    consumable = min(remaining * price, available)  // cast u128
    total_out += consumable
    remaining -= consumable / price
    bin.liquidity_b -= consumable
    bin.liquidity_a += consumable / price
    if bin.liquidity_b == 0:
      current_bin -= 1  // move to cheaper bin
      if current_bin < lower_bin_id: break  // no more liquidity
  
  else (buying RWT with USDC):
    available = bin.liquidity_a  (RWT in this bin)
    price = (1 + bin_step_bps/10000) ^ current_bin
    consumable = min(remaining / price, available)  // cast u128
    total_out += consumable
    remaining -= consumable * price
    bin.liquidity_a -= consumable
    bin.liquidity_b += consumable * price
    if bin.liquidity_a == 0:
      current_bin += 1  // move to more expensive bin
      if current_bin > upper_bin_id: break

active_bin_id = current_bin
amount_out = total_out
Валидация:
  • amount_in > 0
  • Резервы пула ненулевые: reserve_in > 0 && reserve_out > 0 (StandardCurve) или хотя бы один бин имеет ликвидность (Concentrated). Ошибка EmptyReserves, если в пуле нет ликвидности.
  • amount_out ≥ min_amount_out (проверка проскальзывания)
  • amount_out > 0
  • Пул и DEX должны быть активны
Эффект: Пользователь отправляет вход → хранилище. Пул отправляет выход → пользователь. LP-комиссия остаётся внутри резервного vault’а пула на стороне начисления; pool_state.cumulative_fees_per_share_<side> инкрементируется, чтобы LP-холдеры могли забрать долю позже. Протокольная комиссия → areal_fee_account. Комиссия казначейства OT → ot_treasury_fee_account (только для пар с OT). Все комиссии в RWT, взимаются поверх свопа — резервы пула не дилюются. Эмитирует SwapExecuted.
Ветка маршрутизации в mint для Monotonic Ladder (USDC→RWT только на мастер-пулах):Перед стандартным bin walk для a_to_b = false на пуле Monotonic Ladder инструкция свопа оценивает, даст ли mint пользователю лучшую цену:
nav = rwt_vault.nav_book_value                                 // чтение через CPI или из аккаунта
best_ask_price = price_at_bin(active_bin_id + 1)              // самый дешёвый RWT в пуле
has_organic_ask = существует bin > active_bin_id с liquidity_a > 0

mint_cheaper_threshold = nav * (10_000 + 50) / 10_000          // NAV × 1.005 (DEX становится неконкурентен выше этого)

if !has_organic_ask OR best_ask_price > mint_cheaper_threshold:
    // Маршрут в mint — DEX-комиссия не взимается
    CPI rwt_engine::mint_rwt(amount_in_usdc)
      accounts = {
        user, rwt_vault, rwt_mint, user_deposit (USDC),
        user_rwt, capital_accumulator_ata, dao_fee_account
      }
    // Пользователь платит 1% mint fee внутри rwt_engine — делится 0.5% vault / 0.5% DAO
    // Без LP-комиссии, без DEX-протокольной. State пула не меняется.
    emit SwapRoutedToMint { user, usdc_in, rwt_out, nav }
    return

// Иначе: стандартный bin walk от active_bin_id вверх, потребляя organic RWT
// Стандартные DEX-комиссии применяются (LP + протокольная), как в базовой логике выше
Дополнительные аккаунты, требуемые для mint-маршрута (должны передаваться вызывающим; не используются на bin-walk пути):
  • rwt_vault (mut) — передаётся, когда пул мастер-пул
  • rwt_mint (mut)
  • capital_accumulator_ata (mut) — USDC ATA vault
  • dao_fee_account (mut) — должен совпадать с rwt_vault.areal_fee_destination
  • user_rwt (mut)
Почему это работает:
  • Mint всегда проходит, если RWT Engine не на паузе. Vault принимает любое количество USDC по текущему NAV + 1%.
  • Organic ask естественно истощается через эту маршрутизацию (когда существует), пока его цена не поднимется выше mint_cheaper_threshold.
  • Ask-сторона выше NAV × 1.005 никогда не заполняется LP — маршрутизация делает это ненужным. LP-капитал не «ждёт вхолостую» в этих бинах.
Инструкция свопа мастер-пула должна проверять pool_state.pool_type и !a_to_b && non_rwt_mint_is_USDC_or_USDY до вызова ветки маршрутизации в mint. Пулы StandardCurve и пары OT/RWT никогда не маршрутизируются в mint.

Компаундинг доходности

Получение дохода в RWT из Yield Distribution от имени PDA пула, автокомпаундинг дохода OT в резервы пула. Выгодно всем LP-холдерам пропорционально. Примечание: это только для дохода OT — комиссии LP за свопы учитываются отдельно через per-side аккумулятор на PoolState и выводятся индивидуально через claim_lp_fees.
ParameterTypeDescription
cumulative_amountu64Кумулятивная доля пула (из листа merkle)
proofVec<[u8; 32]>Путь merkle proof
Вызывающий: Без ограничений (crank)Аккаунты:
  • crank (signer, mut) — оплачивает инициализацию ClaimStatus
  • pool_state (mut) — обновляет резервы
  • target_vault (mut) — ограничение: должен быть pool.vault_a или pool.vault_b (тот, который RWT)
  • Аккаунты YD CPI: yd_distributor, yd_claim_status, yd_reward_vault
  • yd_program — ограничение: key == YD_PROGRAM_ID
  • token_program, system_program
Логика:
  1. Снимок баланса target_vault до операции
  2. CPI → yield_distribution::claim с PDA пула как claimant
  3. Измерение полученных RWT (после - до)
  4. Добавление полученной суммы в резервы пула (reserve_a или reserve_b)
  5. Эмитирует CompoundYieldExecuted
PDA пула выступает как холдер OT (если пул содержит токены OT в своих резервах). Полученные RWT идут напрямую в резервы, увеличивая стоимость всех LP-позиций одинаково. Индивидуальный вывод для LP не нужен. Минт target_vault валидируется программой YD во время CPI (claimant_token.mint == reward_vault.mint) — crank не может перенаправить RWT в неправильное хранилище. Применимо только к пулам, содержащим OT (например, OT/RWT). Вызов compound_yield на пуле RWT/USDC завершится ошибкой InvalidProof (у пула нет баланса OT → отсутствует в merkle tree).
Вывод накопленных LP-вознаграждений за свопы из резервных vault’ов пула. Каждый LP-холдер выводит пропорционально своей доле в общем объёме LP. Без вестинга — вознаграждения доступны мгновенно. Выплачивает обе стороны токенов пула (A и B); сторона, на которой накопилась комиссия за своп, — это сторона, по которой идёт выплата.Вызывающий: LP-холдер (без ограничений — любой с LP-позицией)Аккаунты:
  • lp_holder (signer) — должен владеть LP-позицией
  • pool_state — читает total_lp_shares и оба cumulative-аккумулятора
  • lp_position (mut) — читает shares, обновляет fees_claimed_per_share_a и fees_claimed_per_share_b
  • pool_vault_a (mut) — резервный vault пула на стороне токена A, владелец — PDA пула
  • pool_vault_b (mut) — резервный vault пула на стороне токена B, владелец — PDA пула
  • recipient_token_a_ata (mut) — ATA холдера на стороне A, получает выведенные A-side комиссии
  • recipient_token_b_ata (mut) — ATA холдера на стороне B, получает выведенные B-side комиссии
  • token_program
Логика:
delta_a = pool_state.cumulative_fees_per_share_a − lp_position.fees_claimed_per_share_a
delta_b = pool_state.cumulative_fees_per_share_b − lp_position.fees_claimed_per_share_b

claimable_a = (delta_a × lp_position.shares) >> 64    // Q64.64 → целое количество токенов
claimable_b = (delta_b × lp_position.shares) >> 64

if claimable_a == 0 && claimable_b == 0: return

if claimable_a > 0:
    transfer claimable_a token_A: pool_vault_a → recipient_token_a_ata (pool PDA signs)
if claimable_b > 0:
    transfer claimable_b token_B: pool_vault_b → recipient_token_b_ata (pool PDA signs)

lp_position.fees_claimed_per_share_a = pool_state.cumulative_fees_per_share_a
lp_position.fees_claimed_per_share_b = pool_state.cumulative_fees_per_share_b
Валидация:
  • lp_position.owner == lp_holder.key()
  • pool_vault_a.amount ≥ claimable_a и pool_vault_b.amount ≥ claimable_b
No-op при нулевых обеих сторонах: если claimable_a == 0 && claimable_b == 0, инструкция возвращает успех без выполнения SPL-трансфера.Эффект: Выведенные суммы переводятся из резервных vault’ов пула в соответствующие ATA LP-холдера. Эмитирует LpFeesClaimed { claimable_a, claimable_b, recipient, ... } — dual-side событие claim.
Как работает per-side аккумулятор. При каждом свопе контракт инкрементирует cumulative-per-share аккумулятор соответствующей стороны: cumulative_fees_per_share_<side> += (fee_lp << 64) / total_lp_shares. Каждая LpPosition снимает снимок этого значения (fees_claimed_per_share_<side>) при последнем взаимодействии. Pending-комиссии вычисляются лениво как (cumulative − snapshot) × shares >> 64, что даёт O(1) учёт на холдера без итерации по LP-холдерам. Аккумулятор хранит fees per share в Q64.64 fixed-point, не абсолютные комиссии — он не переполняется даже при высоком потоке комиссий.Текущее правило свопа. Текущая реализация свопа всегда начисляет LP-комиссию на RWT-стороне пула. Для RWT-парного пула (RWT/USDC, OT/RWT и т.д.) продвигается только один из двух аккумуляторов на каждом свопе, поэтому claimable_<сторона-без-RWT> равен 0 для любой LP-позиции в RWT-парном пуле. Dual-side аккумулятор — forward-compat для не-RWT пар (например OT/USDC) и избегает специальной single-side логики в контракте.

Liquidity Nexus

Управление LP Areal Finance. PDA Nexus владеет LP-позициями и токен-аккаунтами. Бот-менеджер выполняет операции; средства защищены программой (менеджер не может извлечь токены напрямую). Вознаграждения LP-комиссий накапливаются per-side на аккумуляторах пула и выводятся в Areal Treasury через nexus_claim_rewards.
Политика прямого SPL Transfer. Токены, отправленные в токен-аккаунт Nexus через прямой SPL Transfer (т.е. в обход nexus_deposit или nexus_record_deposit), увеличат on-chain баланс Nexus, но НЕ продвинут total_deposited_* — они намеренно нетрекаемые. Такие балансы всё равно тратятся менеджером через nexus_swap / nexus_add_liquidity, но nexus_withdraw_profits высвобождает только разрыв над треком principal floor, поэтому нетрекаемые депозиты эффективно поднимают потолок выводимой прибыли. Не полагайтесь на это для учёта; всегда маршрутизируйте капитал через nexus_deposit (USDC-канал) или withdraw_liquidity_holding в Yield Distribution (RWT-канал).
Создание PDA Liquidity Nexus и установка начального менеджера. Один Nexus на развёртывание DEX (синглтон).
ParameterTypeDescription
managerPubkeyНачальный кошелёк менеджера Nexus (бот)
Вызывающий: Authority (Team Multisig)Аккаунты:
  • authority (signer, mut) — должен соответствовать dex_config.authority
  • dex_config — для валидации authority
  • nexus (init) — PDA seed: ["liquidity_nexus"]
  • system_program
Создаёт:
  • LiquidityNexus PDA — синглтон, владеет LP-позициями
Начальное состояние: manager = manager param, is_active = true
Депозит токенов в PDA Nexus. Стандартный способ добавления капитала в Nexus. Без ограничений — crank-и вызывают после распределения дохода OT или вывода дохода RWT.
ParameterTypeDescription
amountu64Количество токенов для депозита
Вызывающий: Без ограничений (crank)Аккаунты:
  • depositor (signer) — исходный кошелёк
  • nexus (mut) — проверяет is_active
  • depositor_token_account (mut) — ограничение: owner == depositor
  • nexus_token_account (mut) — ограничение: owner == nexus.key()
  • token_mint — валидация принимаемого типа токена
  • token_program
Валидация:
  • amount > 0
  • nexus.is_active == true
  • token_mint == USDC_MINT || token_mint == RWT_MINT (только принимаемые токены)
Логика:
  1. Перевод amount от depositor → nexus ATA
  2. Перевод amount из depositor_token_account в nexus_token_account
  3. Эмитирует NexusDeposited
USDC-канал. OT distribute_revenue отправляет 10% USDC на промежуточный crank-кошелёк, который затем вызывает nexus_deposit напрямую. В будущей итерации USDC-сторона может стейджиться через holding-PDA для паритета с RWT-стороной.RWT-канал. RWT попадает в Nexus атомарно через yield_distribution::withdraw_liquidity_holding: программа YD переводит RWT из RWT ATA PDA LiquidityHolding в RWT ATA Nexus и делает CPI на nexus_record_deposit для обновления principal floor — всё в одной транзакции. На RWT-стороне нет промежуточного crank-кошелька.
Обновление principal floor только в state. Вызывается исключительно как CPI-цель из upstream-программы (сейчас yield_distribution::withdraw_liquidity_holding), которая уже переместила токены в токен-аккаунт Nexus в той же транзакции. SPL-трансфера в этой инструкции нет — только bookkeeping-нога.
ПараметрТипОписание
amountu64Сумма, только что переведённая в Nexus вызывающей программой в той же TX
token_kindu80 = USDC, 1 = RWT
Вызывающий: PDA, принадлежащая upstream-программе (сейчас LiquidityHolding из Yield Distribution), через CPIАккаунты:
  • liquidity_holding (signer) — PDA вызывающего; подпись подтверждает, что bookkeeping-нога атомарна с SPL-трансфером, который вызывающий только что выполнил
  • liquidity_nexus (mut) — инкрементирует total_deposited_usdc или total_deposited_rwt на amount
Валидация:
  • liquidity_nexus.is_active == true
  • token_kind ∈ {0, 1} — только каналы principal’а USDC и RWT
  • Вызывающий — это program-owned signing PDA; прямые подписанты отвергаются
Логика:
  1. Валидация active + token_kind.
  2. Прибавление amount к total_deposited_usdc или total_deposited_rwt (saturating).
  3. Эмитирование NexusDeposited с флагом via_record = true.
Зачем две инструкции? nexus_deposit — для SPL-трансфера + обновления state от обычного подписанта (USDC-канал). nexus_record_deposit — путь только для state, используемый, когда вызывающая программа уже переместила токены через свой CPI и хочет лишь обновить principal floor (RWT-канал). Разделение позволяет избежать повторного блока signer authority SPL-трансфера через границы программ.
Вывод накопленных LP-вознаграждений из резервных vault’ов пула в собственные токен-ATA Nexus. PDA Nexus выступает как LP-холдер и выводит свою пропорциональную долю на обеих сторонах токенов пула. Средства приземляются в ATA Nexus, а не в Areal Treasury напрямую — settlement в Treasury — это отдельный Authority-шаг через nexus_withdraw_profits (только разрыв над principal floor). Только Authority.
ParameterTypeDescription
poolPubkeyПул для вывода комиссий
Вызывающий: Authority (Team Multisig)Аккаунты:
  • authority (signer) — должен соответствовать dex_config.authority
  • dex_config — для валидации authority
  • nexus — PDA, подписывает как LP-холдер
  • pool_state — читает оба cumulative-аккумулятора
  • nexus_lp_position (mut) — LP-позиция Nexus в этом пуле, обновляет fees_claimed_per_share_a и _b
  • pool_vault_a (mut) — резервный vault пула на стороне токена A
  • pool_vault_b (mut) — резервный vault пула на стороне токена B
  • nexus_token_a_ata (mut) — ATA Nexus на стороне токена A, получает выведенные A-side комиссии
  • nexus_token_b_ata (mut) — ATA Nexus на стороне токена B, получает выведенные B-side комиссии
  • token_program
Логика:
  1. Вычисление claimable_a и claimable_b по той же Q64.64-формуле, что и claim_lp_fees.
  2. Перевод claimable_a token-A из pool_vault_anexus_token_a_ata, если > 0; то же для B.
  3. Обновление nexus_lp_position.fees_claimed_per_share_a и _b до текущих cumulative-значений.
  4. Эмитирование LpFeesClaimed { claimable_a, claimable_b, recipient, ... } — то же dual-side событие, что и в user-side claim_lp_fees; распознаётся по recipient == nexus.address().
Двухэтапный settlement в Treasury. Эта инструкция перемещает LP-вознаграждения из резервных vault’ов пула в собственные ATA Nexus. Средства остаются внутри подсистемы Nexus и пополняют withdrawable-profit потолок — они не учитываются в principal floor (total_deposited_* пишут только nexus_deposit / nexus_record_deposit). Для settlement в Areal Treasury Authority затем вызывает nexus_withdraw_profits, который высвобождает до nexus_balance(t) − total_deposited(t) и падает при переполнении.
Используется тот же per-side аккумулятор, что и в claim_lp_fees. Для RWT-парных пулов на практике сегодня ненулевая только RWT-сторона (swap accrual пишет одну сторону за свап), но инструкция выполняет оба перевода безусловно — для forward-compat с не-RWT парами. Both-zero — это чистый no-op (обе стороны пропущены, snapshot всё равно обновляется).
Своп токенов из PDA Nexus через любой пул DEX. Та же логика свопа, что и обычный swap, но PDA Nexus подписывает как пользователь.
ParameterTypeDescription
amount_inu64Количество входного токена
min_amount_outu64Минимальный выход (защита от проскальзывания)
a_to_bboolНаправление свопа
Вызывающий: Только менеджер NexusАккаунты:
  • manager (signer) — должен соответствовать nexus.manager
  • nexus — PDA, подписывает своп как пользователь
  • nexus_token_in (mut) — ограничение: owner == nexus.key()
  • nexus_token_out (mut) — ограничение: owner == nexus.key()
  • pool_state (mut), dex_config, bin_array (опционально)
  • vault_in (mut), vault_out (mut)
  • areal_fee_account (mut)
  • ot_treasury_fee_account (mut, опционально) — обязателен для пар с OT
  • token_program
Валидация:
  • amount_in > 0
  • min_amount_out > 0 (защита от проскальзывания обязательна — предотвращает выполнение свопов менеджером по произвольным ценам)
  • manager == nexus.manager
Эффект: Внутренний CPI к логике swap с PDA Nexus как пользователем. Комиссия казначейства OT взимается для пар с OT. Эмитирует SwapExecuted.
Доверие к менеджеру. Хотя min_amount_out > 0 обязательно, значение устанавливает менеджер. Скомпрометированный менеджер может установить min_amount_out = 1 и выполнять сделки по ужасным ценам. Смягчение: authority (Team Multisig) мониторит события свопов и заменяет менеджера через update_nexus_manager при обнаружении аномальных сделок. Все свопы проверяемы через события SwapExecuted.
Добавление ликвидности из PDA Nexus в любой пул. Работает как zap — принимает любое соотношение токенов, включая один токен. PDA Nexus подписывает как провайдер.
ParameterTypeDescription
amount_au64Количество токена A (может быть 0)
amount_bu64Количество токена B (может быть 0)
min_sharesu128Минимум LP-долей (защита от проскальзывания)
Вызывающий: Только менеджер NexusАккаунты:
  • manager (signer) — должен соответствовать nexus.manager
  • nexus — PDA, подписывает как провайдер
  • pool_state (mut), dex_config
  • lp_position (init_if_needed) — PDA seed: ["lp", pool_state, nexus]
  • nexus_token_a (mut) — ограничение: owner == nexus.key()
  • nexus_token_b (mut) — ограничение: owner == nexus.key()
  • vault_a (mut), vault_b (mut)
  • areal_fee_account (mut) — для комиссии внутреннего свопа при zap
  • bin_array (mut, опционально)
  • token_program, system_program
Эффект: Логика zap (автосвоп для соответствия соотношению) + добавление ликвидности. PDA Nexus как провайдер. Эмитирует LiquidityAdded.
Удаление ликвидности из LP-позиции PDA Nexus.
ParameterTypeDescription
shares_to_burnu128LP-доли для погашения
Вызывающий: Только менеджер NexusАккаунты:
  • manager (signer) — должен соответствовать nexus.manager
  • nexus — PDA, подписывает как провайдер
  • pool_state (mut)
  • lp_position (mut) — ограничение: owner == nexus.key()
  • nexus_token_a (mut), nexus_token_b (mut)
  • vault_a (mut), vault_b (mut)
  • token_program
Эффект: PDA пула подписывает переводы vault→nexus. Эмитирует LiquidityRemoved.
Вывод реализованной прибыли (разрыв над треком principal floor) из токен-ATA Nexus в ATA-получателя. Authority-gated. Падает, если amount > nexus_balance(t) − total_deposited(t).
ПараметрТипОписание
amountu64Сумма прибыли для вывода
token_kindu80 = USDC, 1 = RWT
Вызывающий: Authority (Team Multisig)Аккаунты:
  • authority (signer) — должен совпадать с dex_config.authority
  • dex_config
  • liquidity_nexus (mut)
  • nexus_token_account (mut) — ATA Nexus на запрошенной стороне
  • recipient_token_account (mut) — ATA Areal Treasury (USDC или RWT)
  • mint
  • token_program
Валидация:
  • amount > 0
  • liquidity_nexus.is_active == true
  • token_kind ∈ {0, 1}
  • Инвариант principal-lock: amount ≤ nexus_token_account.balance − total_deposited_{usdc,rwt} (saturating; падение с InsufficientNexusProfit при нарушении)
Логика:
  1. Валидация principal-lock.
  2. PDA Nexus подписывает SPL Transfer amount в recipient_token_account.
  3. Эмитирование NexusProfitsWithdrawn { token_mint, amount, remaining_profit, treasury_destination }.
Principal floor намеренно односторонний (write-only через nexus_deposit / nexus_record_deposit). Чтобы вернуть капитал из Nexus в holding-PDA или Treasury, прибыль должна быть выведена отдельно, а принципал — оставаться on-chain; инструкции nexus_withdraw_principal не существует.
Изменение кошелька менеджера Nexus.
ParameterTypeDescription
new_managerPubkeyНовый кошелёк менеджера
Вызывающий: Authority (Team Multisig)Аккаунты:
  • authority (signer) — должен соответствовать dex_config.authority
  • dex_config
  • nexus (mut)
Эффект: Устанавливает nexus.manager = new_manager. Эмитирует NexusManagerUpdated.

Конфигурация и Authority

Обновление глобальной конфигурации DEX. Полная перезапись.
ParameterTypeDescription
base_fee_bpsu16Новая базовая комиссия (макс. 1 000 = 10%)
lp_fee_share_bpsu16Новая доля LP-комиссии (макс. 10 000 = 100%)
rebalancerPubkeyНовый кошелёк Rebalancer
is_activeboolГлобальный флаг активности DEX
Вызывающий: Authority (Team Multisig)Аккаунты:
  • authority (signer) — должен соответствовать dex_config.authority
  • dex_config (mut)
Валидация:
  • base_fee_bps ≤ 1,000 (макс. 10%)
  • lp_fee_share_bps ≤ 10,000
Эффект: Перезаписывает конфигурацию. Эмитирует DexConfigUpdated.
areal_fee_destination и pause_authority неизменяемы — устанавливаются при инициализации, изменить невозможно. Только обновление программы может их модифицировать.
Добавление или удаление кошелька из вайтлиста создателей пулов.
ParameterTypeDescription
walletPubkeyКошелёк создателя для добавления/удаления
actionCreatorActionAdd или Remove
Вызывающий: Authority (Team Multisig)Аккаунты:
  • authority (signer) — должен соответствовать pool_creators.authority
  • pool_creators (mut)
Валидация:
  • Add: ещё не в вайтлисте, количество < MAX_POOL_CREATORS (10)
  • Remove: должен существовать в вайтлисте
Эффект: Обновляет вайтлист. Эмитирует PoolCreatorsUpdated.
Шаг 1: Текущий authority предлагает нового authority.
ParameterTypeDescription
new_authorityPubkeyПредлагаемый новый authority
Вызывающий: Текущий authorityАккаунты:
  • authority (signer) — должен соответствовать dex_config.authority
  • dex_config (mut)
Валидация: new_authority ≠ current authorityЭффект: Устанавливает dex_config.pending_authority = Some(new_authority). Эмитирует AuthorityTransferProposed.
Повторный вызов перезаписывает существующий pending_authority. Предыдущий предложенный authority теряет возможность принять передачу.
Шаг 2: Предложенный authority принимает.Вызывающий: Новый authority (должен подписать)Аккаунты:
  • new_authority (signer) — должен соответствовать dex_config.pending_authority
  • dex_config (mut)
  • pool_creators (mut) — authority также обновляется здесь
Валидация: signer == dex_config.pending_authorityЭффект: Устанавливает authority = new_authority и в dex_config, и в pool_creators. Очищает pending_authority. Эмитирует AuthorityTransferAccepted.

Экстренные операции

Экстренная пауза отдельного пула. Останавливает свопы и добавление новой ликвидности. НЕ влияет на remove_liquidity (LP всегда могут выйти).Вызывающий: Pause Authority (Team Multisig)Аккаунты:
  • pause_authority (signer) — должен соответствовать dex_config.pause_authority
  • dex_config — для валидации pause_authority
  • pool_state (mut)
Эффект: Устанавливает pool_state.is_active = false. Эмитирует PoolPaused.
Возобновление работы приостановленного пула.Вызывающий: Pause Authority (Team Multisig)Аккаунты:
  • pause_authority (signer) — должен соответствовать dex_config.pause_authority
  • dex_config
  • pool_state (mut)
Эффект: Устанавливает pool_state.is_active = true. Эмитирует PoolUnpaused.

Аккаунты состояния

DexConfig

Глобальный синглтон. Один на развёртывание протокола.
FieldTypeDescription
authorityPubkeyAuthority конфигурации (Team Multisig после начальной настройки)
pending_authorityOption<Pubkey>Целевой адрес ожидающей передачи authority
pause_authorityPubkeyПодписант экстренной паузы (Team Multisig, неизменяемый)
base_fee_bpsu16Базовая комиссия за своп (по умолчанию: 50 = 0,5%)
lp_fee_share_bpsu16Доля LP в комиссии (по умолчанию: 5 000 = 50%)
areal_fee_destinationPubkeyRWT ATA Areal Finance — получает протокольные комиссии в RWT
rebalancerPubkeyКошелёк Pool Rebalancer — единственный подписант, допущенный к вызову grow_liquidity и compress_liquidity
is_activeboolГлобальный аварийный выключатель DEX
bumpu8Bump seed PDA
PDA Seed: ["dex_config"]

PoolState

Один на пару токенов. Хранит резервы, доли, комиссии.
FieldTypeDescription
pool_typePoolTypeStandardCurve или MonotonicLadder
token_a_mintPubkeyМинт токена A
token_b_mintPubkeyМинт токена B
vault_aPubkeyХранилище токена A (authority = PDA пула)
vault_bPubkeyХранилище токена B (authority = PDA пула)
reserve_au64Текущий баланс A
reserve_bu64Текущий баланс B
total_lp_sharesu128Выпущенные LP-доли
fee_bpsu16Комиссия за своп (скопирована из DexConfig при создании)
is_activeboolФлаг активности пула (может быть приостановлен Team Multisig)
total_fees_accumulatedu64Общие комиссии за всё время (LP + протокольные)
cumulative_fees_per_share_au128Q64.64 нарастающая сумма на стороне токена A: cumulative_fees_per_share_a += (fee_lp_a << 64) / total_lp_shares на каждом свопе. O(1) учёт комиссий на холдера
cumulative_fees_per_share_bu128Q64.64 нарастающая сумма на стороне токена B: cumulative_fees_per_share_b += (fee_lp_b << 64) / total_lp_shares на каждом свопе
bin_step_bpsu16Логарифмический шаг бина (0 для StandardCurve; обычно 10 = 0,1% для MonotonicLadder)
active_bin_idi32Текущий активный бин (только MonotonicLadder)
left_anchor_bini32Верх permanent tail (только MonotonicLadder, иммутабельный)
permanent_tail_floor_bini32Низ permanent tail (только MonotonicLadder, иммутабельный)
last_rebalance_nav_bini32NAV bin последнего вызова grow_liquidity или compress_liquidity (только MonotonicLadder)
active_zone_loweri32Нижняя граница текущего active bid wall (только MonotonicLadder)
ot_treasury_fee_destinationOption<Pubkey>RWT ATA PDA OT Treasury. При Some взимается дополнительная комиссия 50 bps на свопах и отправляется сюда. None для пулов без OT (например, RWT/USDC). Устанавливается при создании, неизменяемый.
bumpu8Bump seed PDA
PDA Seed: ["pool", token_a_mint, token_b_mint]

PoolCreators

Вайтлист кошельков, допущенных к созданию пулов. Макс. 10.
FieldTypeDescription
authorityPubkeyКто может добавлять/удалять создателей
creators[Pubkey; 10]Кошельки из вайтлиста
active_countu8Количество активных создателей
bumpu8Bump seed PDA
PDA Seed: ["pool_creators"]

LpPosition

На каждую пару (пул, провайдер) для учёта LP. Создаётся при первом добавлении через init_if_needed.
FieldTypeDescription
poolPubkeyСвязанный пул
ownerPubkeyКошелёк LP-провайдера
sharesu128LP-доли во владении
fees_claimed_per_share_au128Q64.64 снимок PoolState.cumulative_fees_per_share_a, снимаемый при открытии позиции / последнем claim. Pending-комиссии по A: (cumulative_fees_per_share_a − fees_claimed_per_share_a) × shares >> 64
fees_claimed_per_share_bu128Q64.64 снимок PoolState.cumulative_fees_per_share_b. Pending-комиссии по B: (cumulative_fees_per_share_b − fees_claimed_per_share_b) × shares >> 64
last_update_tsi64Метка времени последнего взаимодействия
bumpu8Bump seed PDA
PDA Seed: ["lp", pool_state, provider]

BinArray

Бины ликвидности Monotonic Ladder. Один на пул MonotonicLadder.
FieldTypeDescription
poolPubkeyСвязанный пул
bins[Bin; 1000]Log-scale бины. Каждый бин: liquidity_a: u64 (RWT), liquidity_b: u64 (USDC). Ниже активного: только USDC (bid — permanent tail + extended bid + active bid wall). Выше активного: только RWT (organic ask, не пред-финансируется). Активный бин: оба.
lower_bin_idi32ID bins[0]
bin_step_bpsu16Log-шаг цены между бинами (напр. 10 = 0,1%)
active_bin_idi32Текущий активный бин (двигается только свопами)
bumpu8Bump seed PDA
PDA Seed: ["bins", pool_state]

LiquidityNexus

PDA управления LP Areal Finance. Синглтон — один на DEX. Владеет токен-ATA и LP-позициями. Бот-менеджер выполняет операции; средства защищены программой.
FieldTypeDescription
managerPubkeyКошелёк бота, который может выполнять операции nexus
total_deposited_usdcu64Кумулятивный USDC, внесённый через nexus_deposit
total_deposited_rwtu64Кумулятивный RWT, внесённый через nexus_deposit
is_activeboolФлаг активности
bumpu8Bump seed PDA
PDA Seed: ["liquidity_nexus"]
PDA Nexus владеет LP-позициями (LpPosition, где owner == nexus.key()) и токен-ATA. Менеджер может свопать/добавлять/удалять LP, но НЕ МОЖЕТ переводить токены из ATA Nexus напрямую — только через инструкции DEX. Вознаграждения LP-комиссий выводятся из YD напрямую в Areal Treasury через nexus_claim_rewards. При компрометации менеджера authority заменяет его через update_nexus_manager.

PDA Seeds

AccountSeedsDescription
DexConfig"dex_config"Глобальный синглтон конфигурации
PoolCreators"pool_creators"Вайтлист создателей
PoolState"pool", token_a_mint, token_b_mintПул для каждой пары
LpPosition"lp", pool_state, providerУчёт для каждого LP
BinArray"bins", pool_stateБины Concentrated
LiquidityNexus"liquidity_nexus"LP-менеджер Areal Finance
Аккаунты хранилищ (vault_a, vault_b) НЕ являются PDA — это обычные SPL-аккаунты токенов с authority = pool_state PDA. Создаются как keypair-аккаунты при создании пула.

Константы

ConstantValueDescription
BPS_DENOMINATOR10,000100% в базисных пунктах
DEFAULT_BASE_FEE_BPS50Комиссия за своп 0,5%
DEFAULT_LP_FEE_SHARE_BPS5,00050% комиссии для LP
MAX_FEE_BPS1,000Максимальный лимит комиссии 10%
MAX_BINS1000Бинов на пул MonotonicLadder
GEOMETRIC_R_BPS8500Коэффициент density для active bid wall (r = 0.85)
ACTIVE_ZONE_WIDTH40Бинов в active bid wall
DEFAULT_PERMANENT_TAIL_OFFSET_BPS100Permanent tail стоит на initial_NAV − 1%
DEFAULT_BIN_STEP_BPS100,1% между бинами
MAX_POOL_CREATORS10Макс. создателей в вайтлисте
MIN_LIQUIDITY1,000Минимум долей при первом добавлении (защита от dust)
OT_TREASURY_FEE_BPS50Дополнительная комиссия 0,5% для пар с OT — отправляется на RWT ATA OT Treasury
RWT_MINThardcodedМинт токена RWT — все пулы должны включать RWT (валидируется в create_pool)
USDC_MINThardcodedМинт USDC — для валидации токена в nexus_deposit
OT_PROGRAM_IDhardcodedProgram ID контракта OT — валидируется в create_pool для проверки владения PDA OT Treasury
YD_PROGRAM_IDhardcodedВалидируется в compound_yield

События

EventFieldsWhen
DexInitializedauthority, base_fee_bps, timestampDEX создан
PoolCreatedpool, token_a_mint, token_b_mint, pool_type, creator, ot_treasury_fee_destination, timestampПул создан
LiquidityAddedpool, provider, amount_a, amount_b, shares_minted, timestampLP добавлена
ZapLiquidityExecutedpool, provider, input_a, input_b, swapped_amount, shares_minted, timestampZap: автосвоп + добавление LP
LiquidityRemovedpool, provider, amount_a, amount_b, shares_burned, timestampLP удалена
LiquidityShiftedpool, rebalancer, old_lower, old_upper, new_lower, new_upper, timestampБины ребалансированы
SwapExecutedpool, user, a_to_b, amount_in, amount_out, fee_lp, fee_protocol, fee_ot_treasury, timestampСвоп выполнен
LpFeesClaimedpool, lp_holder, amount, timestampLP-холдер вывел вознаграждения за свопы
CompoundYieldExecutedpool, rwt_claimed, timestampДоход OT автокомпаундирован в пул
PoolCreatorsUpdatedwallet, action, active_count, timestampВайтлист изменён
DexConfigUpdatedbase_fee_bps, lp_fee_share_bps, rebalancer, is_active, timestampКонфигурация изменена
AuthorityTransferProposedcurrent_authority, pending_authority, timestampПередача предложена
AuthorityTransferAcceptedold_authority, new_authority, timestampПередача принята
PoolPausedpool, timestampПул экстренно приостановлен
PoolUnpausedpool, timestampПул возобновлён
NexusInitializedmanager, timestampNexus создан
NexusDepositedtoken_mint, amount, timestampКапитал внесён в Nexus
NexusRewardsClaimedamount, treasury_destination, timestampLP-вознаграждения выведены из YD в Areal Treasury
NexusProfitsWithdrawntoken_mint, amount, remaining_profit, treasury_destination, timestampПрибыль выведена в Areal Treasury
NexusManagerUpdatedold_manager, new_manager, timestampМенеджер Nexus изменён

Коды ошибок

ErrorDescription
UnauthorizedНе является authority DEX
CreatorNotWhitelistedНе в вайтлисте создателей пулов
DexPausedГлобальный DEX is_active = false
PoolNotActiveПул is_active = false
WhitelistFullМакс. 10 создателей
IdenticalMintstoken_a == token_b
CreatorNotFoundRemove: создатель не в вайтлисте
ZeroAmountAmount = 0
InsufficientLiquidityРезервы пула пусты
InsufficientSharesУ LP меньше долей, чем запрошено для сжигания
InitialLiquidityTooSmallПервое добавление < MIN_LIQUIDITY
SlippageExceededВыход < min_amount_out
ZeroOutputСвоп произведёт 0
EmptyReservesНевозможно свопнуть с нулевыми резервами
MathOverflowАрифметическое переполнение
InvalidFeebase_fee_bps > MAX_FEE_BPS
InvalidFeeSharelp_fee_share_bps > 10 000
InvalidBinRangelower ≥ upper или вне границ
BinOutOfRangeID бина вне BinArray
InsufficientBinLiquidityНет ликвидности в бинах для свопа
InvalidBinStepbin_step_bps = 0 для concentrated
MissingRwtMintНи token_a, ни token_b не является RWT_MINT
InvalidMintOrdertoken_a_mint >= token_b_mint (должен быть каноничный порядок)
InvalidVaulttarget_vault не является vault_a или vault_b
NothingToCompoundНе получено RWT из YD
SelfTransferНевозможно передать authority самому себе
NoPendingAuthorityНет ожидающей передачи
InvalidPendingAuthorityПодписант ≠ pending_authority
InvalidOtTreasuryDestinationot_treasury_fee_destination не соответствует вычисленному RWT ATA для PDA OT Treasury, или PDA OT Treasury не принадлежит OT_PROGRAM_ID
MissingOtTreasuryAccountУ пула установлен ot_treasury_fee_destination, но ot_treasury_fee_account не предоставлен в свопе
InvalidNexusTokentoken_mint в nexus_deposit не является USDC_MINT или RWT_MINT
NexusNotActiveNexus is_active = false
InvalidNexusManagerПодписант ≠ nexus.manager
NexusClaimFailedPDA Nexus не найден в merkle tree или proof невалиден

Архитектура и руководство по интеграции

Кросс-программная интеграция

  • RWT Engine vault_swap делает CPI к native_dex::swap
  • PDA RWT Vault подписывает как пользователь
  • Менеджер контролирует направление и проскальзывание
  • YD convert_to_rwt делает CPI к native_dex::swap (покупка RWT ниже NAV)
  • PDA YD Accumulator подписывает как пользователь
  • PDA Nexus живёт внутри контракта DEX — владеет LP-позициями и токен-ATA
  • Капитал поступает через nexus_deposit: 10% дохода OT (USDC) + 15% дохода RWT Engine (RWT)
  • Бот-менеджер вызывает nexus_swap, nexus_add_liquidity, nexus_remove_liquidity
  • Вознаграждения LP-комиссий выводятся из YD в Areal Treasury через nexus_claim_rewards (Authority)
  • Менеджер заменяется authority DEX (Team Multisig) через update_nexus_manager
  • PDA пула получает доход в RWT от OT из YD через CPI → автокомпаундинг в резервы пула
  • Все LP-холдеры получают выгоду пропорционально (более глубокий пул = лучшие сделки)
  • LP-комиссии за свопы обрабатываются отдельно через per-side аккумулятор на PoolState (claim_lp_fees)
  • Бот Rebalancer вызывает grow_liquidity на пулах Monotonic Ladder, когда NAV растёт выше порога отклонения 1% — расширяет bid wall вправо, используя USDC из Nexus
  • Бот Rebalancer вызывает compress_liquidity после governance-writedown — пересчитывает density вокруг нового (более низкого) NAV; organic ask RWT выше нового NAV остаётся как frozen ask wall
  • Кошелёк Rebalancer подписывает как signer (выделенная ключевая пара, не PDA)

Допущения о доверии

Доверие к создателям пулов: Создатели из вайтлиста могут устанавливать начальные параметры пула (bin_step). Злоумышленный создатель может установить невыгодную начальную цену при первом добавлении ликвидности. Смягчение: authority (Team Multisig) контролирует вайтлист.
Зависимость compound_yield от YD: Использует захардкоженный program ID YD для CPI. Если программа YD обновлена на новый адрес, compound_yield должен быть обновлён через обновление программы.

Чеклист развёртывания

Предварительные условия: Yield Distribution должен быть развёрнут (необходим для CPI compound_yield).
  1. Вызвать initialize_dex с areal_fee_destination (RWT ATA Areal Finance), pause_authority (Team Multisig), rebalancer (кошелёк бота)
  2. Вызвать initialize_nexus с manager (кошелёк бота Nexus)
  3. Создать ATA Nexus — создать USDC ATA и RWT ATA, принадлежащие PDA Nexus (необходимы до того, как адреса назначения OT и конфигурация RWT Engine будут на них указывать)
  4. Добавить создателей пулов через update_pool_creators
  5. Создать пулы — все пулы образуют пару с RWT (например, OT/RWT, RWT/USDC). Минты должны быть в каноничном порядке (token_a < token_b). Для пар с OT: передать PDA ot_treasury и его RWT ATA — OT Treasury должен быть инициализирован через контракт OT до создания пула
  6. Передать authority в Team Multisig через propose_authority_transfer + accept_authority_transfer

Сводка потоков токенов

FromToMechanismWho triggers
Токен пользователя (вход)Хранилище пула (vault_in)swapПользователь
Хранилище пула (vault_out)Токен пользователя (выход)swapПользователь
Комиссия за своп (доля LP)Остаётся в резервном vault’е пула на стороне начисления; учитывается через cumulative_fees_per_share_<side> на PoolStateswapАвтоматически
Резервные vault’ы пула (A и B)ATA LP-холдера (A и B)claim_lp_fees (per-side Q64.64 выплата)LP-холдер
Комиссия за своп (доля протокола)RWT ATA Areal Finance (всегда в RWT)swapАвтоматически
Комиссия за своп (казначейство OT)RWT ATA OT Treasury (только пары с OT, всегда в RWT)swapАвтоматически
LP-токены A+BХранилища пулаadd_liquidityLP-провайдер
LP любое соотношение / один токенХранилища пула (через внутренний своп)zap_liquidityLP-провайдер
Хранилища пулаLP-токены A+Bremove_liquidityLP-провайдер
YD reward vaultРезервы пула (RWT)compound_yield (CPI к YD)Crank
Доход OT (10% USDC)USDC ATA crank → USDC ATA NexusOT distribute_revenuenexus_deposit (два шага: crank получает, затем вносит)Crank
Доход RWT Engine (15% RWT)RWT ATA LiquidityHolding → RWT ATA NexusYD withdraw_liquidity_holding (атомарный drain в одну TX + CPI nexus_record_deposit)Authority
LP-вознаграждения Nexus (токены A и/или B)ATA Nexus (A и B)nexus_claim_rewards (per-side Q64.64 выплата; settlement в Treasury — отдельный вызов nexus_withdraw_profits)Authority
Токены NexusХранилища пулаnexus_add_liquidityМенеджер Nexus
Хранилища пулаТокены Nexusnexus_remove_liquidityМенеджер Nexus
Токен Nexus (USDC или RWT)ATA Areal Treasurynexus_withdraw_profits (только разрыв над principal floor)Authority

См. также

  • Liquidity Nexus — обзор подсистемы, инвариант principal-lock, три уровня доверия и два канала депозита, питающих Nexus
  • Контракт Yield Distribution — PDA LiquidityHolding и withdraw_liquidity_holding (атомарный drain RWT-канала с CPI на nexus_record_deposit)
  • Контракт RWT Engineclaim_yield и дележ 70 / 15 / 15, формирующий 15% долю RWT, маршрутизируемую через RWT-канал