Native DEX
✅ Готово к разработке
PoolState, забираются через claim_lp_fees) и Areal Finance (протокольная комиссия в RWT), взимаются поверх свопа для сохранения капитализации пула. OT-пары взимают дополнительные 0,5% в казначейство OT-проекта. Мастер-пулы маршрутизируют USDC→RWT сделки в rwt_engine::mint_rwt, когда DEX ask неконкурентен — на mint-пути DEX-комиссия не взимается.
Ключевые концепции
23 инструкции
6 аккаунтов состояния
6 PDA Seeds
Два типа пулов
StandardCurve
- Простая, проверенная математика AMM
- Непрерывное ценообразование:
price = reserve_b / reserve_a - LP-доли:
sqrt(a × b)при первом добавлении, пропорционально после - Используется для: пар OT/RWT, сторонних пар
Monotonic Ladder
- 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 askcompress_liquidity(Rebalancer) пересчитывает density при writedown — замороженные ask RWT выше нового NAV сохраняются для восстановления- Используется исключительно для: мастер-пулов RWT/USDY и RWT/USDC
mint_rwt, которая производит RWT по детерминированной потолочной цене NAV × 1.01. Это делает любой пред-депонированный RWT ask-сайд выше NAV × 1.005 экономически мёртвым капиталом — покупатели всегда предпочтут mint. Monotonic Ladder помещает 100% Nexus LP в дефицитный ресурс (USDC bid depth) и делегирует ask-сторону самому vault через маршрутизацию в mint.Архитектура комиссий
Базовая комиссия
Распределение комиссии
- 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).
Комиссии поверх свопа (без размывания пула)
amount_in + fees (дополнительные RWT сверху). Если пользователь покупает RWT: пользователь получает amount_out полностью, комиссии берутся отдельно из валового выхода RWT перед доставкой.Всегда в RWT
LP-комиссия → per-side аккумулятор (мгновенный вывод)
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 пар.Комиссия казначейства OT (только для пар с OT)
OT_TREASURY_FEE_BPS = 50 — изменяется только через обновление программы.Пропуск комиссии на mint-пути (только мастер-пулы)
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
update_dex_config— комиссии, адреса назначения, rebalancerupdate_pool_creators— управление вайтлистомpropose_authority_transfer
🛑 Pause Authority
pause_pool/unpause_pool
⚖️ Rebalancer
grow_liquidity— расширение bid wall вправо при росте NAVcompress_liquidity— перенос центра density при writedown
update_dex_config.💧 Nexus Manager
nexus_swap/nexus_add_liquidity/nexus_remove_liquidity
update_nexus_manager.🏗️ Pool Creators
create_pool/create_concentrated_pool
🌐 Без ограничений
swap— торговля токенамиadd_liquidity/zap_liquidity/remove_liquiditycompound_yield— автокомпаундинг RWT
Инструкции
Инициализация
initialize_dex
initialize_dex
| Parameter | Type | Description |
|---|---|---|
areal_fee_destination | Pubkey | RWT ATA Areal Finance — получает протокольные комиссии в RWT (статический, неизменяемый) |
pause_authority | Pubkey | Подписант экстренной паузы (Team Multisig, неизменяемый) |
rebalancer | Pubkey | Кошелёк Pool Rebalancer — может вызывать grow_liquidity и compress_liquidity |
authority(signer, mut) — deployer, оплачивает созданиеdex_config(init) — PDA seed:["dex_config"]pool_creators(init) — PDA seed:["pool_creators"]system_program
DexConfigPDA — глобальный синглтон конфигурацииPoolCreatorsPDA — вайтлист (deployer автоматически добавляется первым создателем)
base_fee_bps = 50(0,5%)lp_fee_share_bps = 5,000(50% комиссии для LP)is_active = true
Создание пулов
create_pool
create_pool
creator(signer, mut) — должен быть в вайтлисте pool_creators, оплачивает создание аккаунтовdex_config— проверяет is_active, предоставляет base_fee_bps и lp_fee_share_bpspool_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.keyvault_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, принадлежащий PDAot_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при невыполнении любой проверки.
ot_treasury_fee_destination = ot_treasury_rwt_ata.key() если предоставлены аккаунты OT treasury, иначе None.fee_bps пула копируется из DexConfig при создании и неизменяем после этого. Изменение base_fee_bps в DexConfig влияет только на будущие пулы. Для изменения комиссии существующего пула требуется обновление программы. Аналогично, ot_treasury_fee_destination устанавливается при создании и неизменяем.create_concentrated_pool
create_concentrated_pool
| Parameter | Type | Description |
|---|---|---|
bin_step_bps | u16 | Логарифмический шаг цены между бинами (по умолчанию: 10 = 0,1%) |
initial_active_bin | i32 | Начальный ID активного бина — якорит initial NAV в log-scale |
permanent_tail_offset_bps | i32 | Позиция 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после создания пула.
Ликвидность
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.add_liquidity
add_liquidity
| Parameter | Type | Description |
|---|---|---|
amount_a | u64 | Количество токена A |
amount_b | u64 | Количество токена B |
provider(signer) — владеет токен-аккаунтамиpayer(signer, mut) — оплачивает ренту за инициализацию LpPositionpool_state(mut) — обновляет резервы, lp_shareslp_position(init_if_needed) — PDA seed:["lp", pool_state, provider]provider_token_a(mut) — ограничение:owner == provider,mint == pool.token_a_mintprovider_token_b(mut) — ограничение:owner == provider,mint == pool.token_b_mintvault_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()означает, что избыточные токены одной стороны не вносятся — используется только пропорциональная сумма. Неиспользованные токены остаются в кошельке пользователя.
LiquidityAdded.add_liquidity доступен только на пулах StandardCurve. На мастер-пулах Monotonic Ladder (RWT/USDC, RWT/USDY) пользовательский add_liquidity и zap_liquidity падает с MasterPoolUserLpDisabled. Рост bid-стороны мастер-пулов происходит исключительно через grow_liquidity, финансируемый из аккумулятора Nexus. Обоснование — в секции Monotonic Ladder в блоке Два типа пулов.zap_liquidity
zap_liquidity
| Parameter | Type | Description |
|---|---|---|
amount_a | u64 | Количество токена A (может быть 0) |
amount_b | u64 | Количество токена B (может быть 0) |
min_shares | u128 | Минимум LP-долей для получения (защита от проскальзывания) |
provider(signer) — владеет токен-аккаунтамиpayer(signer, mut) — оплачивает ренту за инициализацию LpPositionpool_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_mintprovider_token_b(mut) — ограничение:owner == provider,mint == pool.token_b_mintvault_a(mut),vault_b(mut) — хранилища пулаareal_fee_account(mut) — получает протокольную комиссию от внутреннего свопаot_treasury_fee_account(mut, опционально) — получает комиссию казначейства OT от внутреннего свопа (обязателен для пар с OT)bin_array(mut, опционально) — обязателен для пулов Concentratedtoken_program,system_program
amount_a > 0 || amount_b > 0(минимум один токен)- Пул должен быть активен
- Чтение текущего соотношения пула. Если пул пуст (reserves = 0): пропуск свопа, обработка как обычный
add_liquidityс обеими суммами как есть. Иначе:target_ratio = reserve_a / reserve_b - Расчёт количества для свопа для соответствия соотношению:
- После внутреннего свопа: соотношение пула изменилось, пересчёт оптимальных сумм для добавления
- Добавление ликвидности сбалансированными суммами (та же математика, что и
add_liquidity) shares ≥ min_shares(проверка проскальзывания)- Эмитирует
ZapLiquidityExecuted
amount_a = 0, весь amount_b делится — половина свопается в токен A, половина остаётся как токен B. Аналогично в обратную сторону. Это простейший UX: «внесите USDC, получите LP-доли.»cumulative_fees_per_share_<side>, протокольная комиссия → Areal Finance, комиссия казначейства OT → OT Treasury для пар с OT). Это сделано намеренно — zap не должен быть лазейкой без комиссий.remove_liquidity
remove_liquidity
| Parameter | Type | Description |
|---|---|---|
shares_to_burn | u128 | LP-доли для погашения |
provider(signer) — должен соответствоватьlp_position.ownerpool_state(mut)lp_position(mut) — ограничение:owner == provider,pool == pool_stateprovider_token_a(mut),provider_token_b(mut)vault_a(mut),vault_b(mut)token_program
shares_to_burn ≤ lp_position.shares- Работает даже когда пул на паузе (LP всегда может выйти)
lp_position.shares == 0 после сжигания, аккаунт LpPosition закрывается, рента возвращается провайдеру. Эмитирует LiquidityRemoved.grow_liquidity
grow_liquidity
| Parameter | Type | Description |
|---|---|---|
new_nav_bin | i32 | Целевой NAV bin ID, вычисленный офф-чейн Rebalancer: new_nav_bin = log(nav_book_value) / log(1 + bin_step_bps/10000) |
active_zone_width | u16 | Количество бинов в active bid wall (по умолчанию: 40; ~4% ценового диапазона при шаге 0.1%). Расположен справа-вниз от new_nav_bin, через extended bid |
dex_config.rebalancer)Аккаунты:rebalancer(signer)dex_config— валидирует rebalancerpool_state(mut) — должен быть типа Monotonic Ladderbin_array(mut)liquidity_nexus(mut) — источник свежего USDCnexus_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
-
Определение диапазонов:
-
Вычисление целевых density weights (geometric,
r = GEOMETRIC_R_BPS / 10000, по умолчанию 8500 = 0.85): -
Расчёт требуемого USDC:
-
Извлечение из Nexus: перевод
nexus_availableUSDC изnexus_usdc_ata→pool_vault_b. Аккумулятор Nexus обнуляется (остаток сохраняется для следующего цикла). -
Перераспределение по новой active zone:
-
Permanent tail:
[tail_lower .. tail_upper]не трогается. Никогда не ребалансируется. -
Organic ask:
[new_nav_bin + 1 .. MAX_BINS]не трогается. Содержит RWT от прошлых продаж пользователей, если были. -
Обновление state:
pool_state.last_rebalance_nav_bin = new_nav_bin,pool_state.active_zone_lower = new_active_lo,active_bin_idgrow_liquidity НЕ меняет (только свопы двигают его).
LiquidityGrown.compress_liquidity
compress_liquidity
rwt_engine::adjust_capital). Только Rebalancer. Редкий случай. Без притока токенов — чисто перераспределение весов внутри существующего капитала.| Parameter | Type | Description |
|---|---|---|
new_nav_bin | i32 | Более низкий целевой NAV bin ID |
active_zone_width | u16 | Та же семантика, что и у grow_liquidity |
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)
- Определение новой active zone:
[new_nav_bin − active_zone_width/2 .. new_nav_bin]. - Поглощение бывшей active zone в extended bid: бины, которые были в active zone, но теперь сидят выше
new_nav_bin(они держали USDC на пике density), сливаются с extended bid — сразу становятся естественной stress-buffer глубиной. - Пересчёт geometric density weights вокруг
new_nav_bin, используя только существующий USDC-капитал в новой active zone (без забора из Nexus на compress). - Перераспределение USDC внутри новой active zone по весам. Бывшие extended bid бины далеко ниже нового NAV остаются как есть.
- Organic ask НЕ трогается. RWT, который был в ask-бинах выше старого NAV, но теперь выше ещё более низкого нового NAV, остаётся там как замороженная ask-стенка — если NAV восстановится через будущую доходность, эти позиции снова станут productive без ребалансировки.
- Permanent tail НЕ трогается.
- Обновление state:
pool_state.last_rebalance_nav_bin = new_nav_bin,pool_state.active_zone_lower = new_active_lo.
LiquidityCompressed.Своп
swap
swap
| Parameter | Type | Description |
|---|---|---|
amount_in | u64 | Количество входного токена |
min_amount_out | u64 | Минимальный выход (защита от проскальзывания) |
a_to_b | bool | Направление свопа |
user(signer)pool_state(mut) — должен быть активенdex_config— должен быть активенbin_array(mut, опционально) — обязателен только для пулов Concentrateduser_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
fee_lp→ остаётся внутри резервного vault’а пула на стороне начисления (сейчас — всегда RWT-сторона); контракт инкрементируетpool_state.cumulative_fees_per_share_<side>, чтобы LP-холдеры могли позже забрать свою долю черезclaim_lp_feesfee_protocol→ RWT переводится наareal_fee_account(RWT ATA Areal Finance)ot_treasury_fee→ RWT переводится наot_treasury_fee_account(только для пар с OT)- Для пулов без OT
ot_treasury_fee = 0(без дополнительной комиссии)
amount_in > 0- Резервы пула ненулевые:
reserve_in > 0 && reserve_out > 0(StandardCurve) или хотя бы один бин имеет ликвидность (Concentrated). ОшибкаEmptyReserves, если в пуле нет ликвидности. amount_out ≥ min_amount_out(проверка проскальзывания)amount_out > 0- Пул и DEX должны быть активны
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 пользователю лучшую цену:rwt_vault(mut) — передаётся, когда пул мастер-пулrwt_mint(mut)capital_accumulator_ata(mut) — USDC ATA vaultdao_fee_account(mut) — должен совпадать сrwt_vault.areal_fee_destinationuser_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.Компаундинг доходности
compound_yield
compound_yield
PoolState и выводятся индивидуально через claim_lp_fees.| Parameter | Type | Description |
|---|---|---|
cumulative_amount | u64 | Кумулятивная доля пула (из листа merkle) |
proof | Vec<[u8; 32]> | Путь merkle proof |
crank(signer, mut) — оплачивает инициализацию ClaimStatuspool_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_IDtoken_program,system_program
- Снимок баланса target_vault до операции
- CPI →
yield_distribution::claimс PDA пула как claimant - Измерение полученных RWT (после - до)
- Добавление полученной суммы в резервы пула (reserve_a или reserve_b)
- Эмитирует
CompoundYieldExecuted
target_vault валидируется программой YD во время CPI (claimant_token.mint == reward_vault.mint) — crank не может перенаправить RWT в неправильное хранилище. Применимо только к пулам, содержащим OT (например, OT/RWT). Вызов compound_yield на пуле RWT/USDC завершится ошибкой InvalidProof (у пула нет баланса OT → отсутствует в merkle tree).claim_lp_fees
claim_lp_fees
lp_holder(signer) — должен владеть LP-позициейpool_state— читаетtotal_lp_sharesи оба cumulative-аккумулятораlp_position(mut) — читаетshares, обновляетfees_claimed_per_share_aиfees_claimed_per_share_bpool_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
lp_position.owner == lp_holder.key()pool_vault_a.amount ≥ claimable_aиpool_vault_b.amount ≥ claimable_b
claimable_a == 0 && claimable_b == 0, инструкция возвращает успех без выполнения SPL-трансфера.Эффект: Выведенные суммы переводятся из резервных vault’ов пула в соответствующие ATA LP-холдера. Эмитирует LpFeesClaimed { claimable_a, claimable_b, recipient, ... } — dual-side событие claim.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.
initialize_nexus
initialize_nexus
| Parameter | Type | Description |
|---|---|---|
manager | Pubkey | Начальный кошелёк менеджера Nexus (бот) |
authority(signer, mut) — должен соответствоватьdex_config.authoritydex_config— для валидации authoritynexus(init) — PDA seed:["liquidity_nexus"]system_program
LiquidityNexusPDA — синглтон, владеет LP-позициями
manager = manager param, is_active = truenexus_deposit
nexus_deposit
| Parameter | Type | Description |
|---|---|---|
amount | u64 | Количество токенов для депозита |
depositor(signer) — исходный кошелёкnexus(mut) — проверяет is_activedepositor_token_account(mut) — ограничение:owner == depositornexus_token_account(mut) — ограничение:owner == nexus.key()token_mint— валидация принимаемого типа токенаtoken_program
amount > 0nexus.is_active == truetoken_mint == USDC_MINT || token_mint == RWT_MINT(только принимаемые токены)
- Перевод
amountот depositor → nexus ATA - Перевод
amountизdepositor_token_accountвnexus_token_account - Эмитирует
NexusDeposited
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-кошелька.nexus_record_deposit
nexus_record_deposit
yield_distribution::withdraw_liquidity_holding), которая уже переместила токены в токен-аккаунт Nexus в той же транзакции. SPL-трансфера в этой инструкции нет — только bookkeeping-нога.| Параметр | Тип | Описание |
|---|---|---|
amount | u64 | Сумма, только что переведённая в Nexus вызывающей программой в той же TX |
token_kind | u8 | 0 = USDC, 1 = RWT |
LiquidityHolding из Yield Distribution), через CPIАккаунты:liquidity_holding(signer) — PDA вызывающего; подпись подтверждает, что bookkeeping-нога атомарна с SPL-трансфером, который вызывающий только что выполнилliquidity_nexus(mut) — инкрементируетtotal_deposited_usdcилиtotal_deposited_rwtнаamount
liquidity_nexus.is_active == truetoken_kind ∈ {0, 1}— только каналы principal’а USDC и RWT- Вызывающий — это program-owned signing PDA; прямые подписанты отвергаются
- Валидация active +
token_kind. - Прибавление
amountкtotal_deposited_usdcилиtotal_deposited_rwt(saturating). - Эмитирование
NexusDepositedс флагомvia_record = true.
nexus_deposit — для SPL-трансфера + обновления state от обычного подписанта (USDC-канал). nexus_record_deposit — путь только для state, используемый, когда вызывающая программа уже переместила токены через свой CPI и хочет лишь обновить principal floor (RWT-канал). Разделение позволяет избежать повторного блока signer authority SPL-трансфера через границы программ.nexus_claim_rewards
nexus_claim_rewards
nexus_withdraw_profits (только разрыв над principal floor). Только Authority.| Parameter | Type | Description |
|---|---|---|
pool | Pubkey | Пул для вывода комиссий |
authority(signer) — должен соответствоватьdex_config.authoritydex_config— для валидации authoritynexus— PDA, подписывает как LP-холдерpool_state— читает оба cumulative-аккумулятораnexus_lp_position(mut) — LP-позиция Nexus в этом пуле, обновляетfees_claimed_per_share_aи_bpool_vault_a(mut) — резервный vault пула на стороне токена Apool_vault_b(mut) — резервный vault пула на стороне токена Bnexus_token_a_ata(mut) — ATA Nexus на стороне токена A, получает выведенные A-side комиссииnexus_token_b_ata(mut) — ATA Nexus на стороне токена B, получает выведенные B-side комиссииtoken_program
- Вычисление
claimable_aиclaimable_bпо той же Q64.64-формуле, что иclaim_lp_fees. - Перевод
claimable_atoken-A изpool_vault_a→nexus_token_a_ata, если> 0; то же для B. - Обновление
nexus_lp_position.fees_claimed_per_share_aи_bдо текущих cumulative-значений. - Эмитирование
LpFeesClaimed { claimable_a, claimable_b, recipient, ... }— то же dual-side событие, что и в user-sideclaim_lp_fees; распознаётся поrecipient == nexus.address().
total_deposited_* пишут только nexus_deposit / nexus_record_deposit). Для settlement в Areal Treasury Authority затем вызывает nexus_withdraw_profits, который высвобождает до nexus_balance(t) − total_deposited(t) и падает при переполнении.claim_lp_fees. Для RWT-парных пулов на практике сегодня ненулевая только RWT-сторона (swap accrual пишет одну сторону за свап), но инструкция выполняет оба перевода безусловно — для forward-compat с не-RWT парами. Both-zero — это чистый no-op (обе стороны пропущены, snapshot всё равно обновляется).nexus_swap
nexus_swap
swap, но PDA Nexus подписывает как пользователь.| Parameter | Type | Description |
|---|---|---|
amount_in | u64 | Количество входного токена |
min_amount_out | u64 | Минимальный выход (защита от проскальзывания) |
a_to_b | bool | Направление свопа |
manager(signer) — должен соответствоватьnexus.managernexus— 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, опционально) — обязателен для пар с OTtoken_program
amount_in > 0min_amount_out > 0(защита от проскальзывания обязательна — предотвращает выполнение свопов менеджером по произвольным ценам)manager == nexus.manager
swap с PDA Nexus как пользователем. Комиссия казначейства OT взимается для пар с OT. Эмитирует SwapExecuted.nexus_add_liquidity
nexus_add_liquidity
| Parameter | Type | Description |
|---|---|---|
amount_a | u64 | Количество токена A (может быть 0) |
amount_b | u64 | Количество токена B (может быть 0) |
min_shares | u128 | Минимум LP-долей (защита от проскальзывания) |
manager(signer) — должен соответствоватьnexus.managernexus— PDA, подписывает как провайдерpool_state(mut),dex_configlp_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) — для комиссии внутреннего свопа при zapbin_array(mut, опционально)token_program,system_program
LiquidityAdded.nexus_remove_liquidity
nexus_remove_liquidity
| Parameter | Type | Description |
|---|---|---|
shares_to_burn | u128 | LP-доли для погашения |
manager(signer) — должен соответствоватьnexus.managernexus— 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
LiquidityRemoved.nexus_withdraw_profits
nexus_withdraw_profits
amount > nexus_balance(t) − total_deposited(t).| Параметр | Тип | Описание |
|---|---|---|
amount | u64 | Сумма прибыли для вывода |
token_kind | u8 | 0 = USDC, 1 = RWT |
authority(signer) — должен совпадать сdex_config.authoritydex_configliquidity_nexus(mut)nexus_token_account(mut) — ATA Nexus на запрошенной сторонеrecipient_token_account(mut) — ATA Areal Treasury (USDC или RWT)minttoken_program
amount > 0liquidity_nexus.is_active == truetoken_kind ∈ {0, 1}- Инвариант principal-lock:
amount ≤ nexus_token_account.balance − total_deposited_{usdc,rwt}(saturating; падение сInsufficientNexusProfitпри нарушении)
- Валидация principal-lock.
- PDA Nexus подписывает SPL Transfer
amountвrecipient_token_account. - Эмитирование
NexusProfitsWithdrawn { token_mint, amount, remaining_profit, treasury_destination }.
nexus_deposit / nexus_record_deposit). Чтобы вернуть капитал из Nexus в holding-PDA или Treasury, прибыль должна быть выведена отдельно, а принципал — оставаться on-chain; инструкции nexus_withdraw_principal не существует.update_nexus_manager
update_nexus_manager
| Parameter | Type | Description |
|---|---|---|
new_manager | Pubkey | Новый кошелёк менеджера |
authority(signer) — должен соответствоватьdex_config.authoritydex_confignexus(mut)
nexus.manager = new_manager. Эмитирует NexusManagerUpdated.Конфигурация и Authority
update_dex_config
update_dex_config
| Parameter | Type | Description |
|---|---|---|
base_fee_bps | u16 | Новая базовая комиссия (макс. 1 000 = 10%) |
lp_fee_share_bps | u16 | Новая доля LP-комиссии (макс. 10 000 = 100%) |
rebalancer | Pubkey | Новый кошелёк Rebalancer |
is_active | bool | Глобальный флаг активности DEX |
authority(signer) — должен соответствоватьdex_config.authoritydex_config(mut)
base_fee_bps ≤ 1,000(макс. 10%)lp_fee_share_bps ≤ 10,000
DexConfigUpdated.areal_fee_destination и pause_authority неизменяемы — устанавливаются при инициализации, изменить невозможно. Только обновление программы может их модифицировать.update_pool_creators
update_pool_creators
| Parameter | Type | Description |
|---|---|---|
wallet | Pubkey | Кошелёк создателя для добавления/удаления |
action | CreatorAction | Add или Remove |
authority(signer) — должен соответствоватьpool_creators.authoritypool_creators(mut)
- Add: ещё не в вайтлисте, количество < MAX_POOL_CREATORS (10)
- Remove: должен существовать в вайтлисте
PoolCreatorsUpdated.propose_authority_transfer
propose_authority_transfer
accept_authority_transfer
accept_authority_transfer
Экстренные операции
pause_pool
pause_pool
pause_authority(signer) — должен соответствоватьdex_config.pause_authoritydex_config— для валидации pause_authoritypool_state(mut)
pool_state.is_active = false. Эмитирует PoolPaused.unpause_pool
unpause_pool
pause_authority(signer) — должен соответствоватьdex_config.pause_authoritydex_configpool_state(mut)
pool_state.is_active = true. Эмитирует PoolUnpaused.Аккаунты состояния
DexConfig
Глобальный синглтон. Один на развёртывание протокола.| Field | Type | Description |
|---|---|---|
authority | Pubkey | Authority конфигурации (Team Multisig после начальной настройки) |
pending_authority | Option<Pubkey> | Целевой адрес ожидающей передачи authority |
pause_authority | Pubkey | Подписант экстренной паузы (Team Multisig, неизменяемый) |
base_fee_bps | u16 | Базовая комиссия за своп (по умолчанию: 50 = 0,5%) |
lp_fee_share_bps | u16 | Доля LP в комиссии (по умолчанию: 5 000 = 50%) |
areal_fee_destination | Pubkey | RWT ATA Areal Finance — получает протокольные комиссии в RWT |
rebalancer | Pubkey | Кошелёк Pool Rebalancer — единственный подписант, допущенный к вызову grow_liquidity и compress_liquidity |
is_active | bool | Глобальный аварийный выключатель DEX |
bump | u8 | Bump seed PDA |
["dex_config"]
PoolState
Один на пару токенов. Хранит резервы, доли, комиссии.| Field | Type | Description |
|---|---|---|
pool_type | PoolType | StandardCurve или MonotonicLadder |
token_a_mint | Pubkey | Минт токена A |
token_b_mint | Pubkey | Минт токена B |
vault_a | Pubkey | Хранилище токена A (authority = PDA пула) |
vault_b | Pubkey | Хранилище токена B (authority = PDA пула) |
reserve_a | u64 | Текущий баланс A |
reserve_b | u64 | Текущий баланс B |
total_lp_shares | u128 | Выпущенные LP-доли |
fee_bps | u16 | Комиссия за своп (скопирована из DexConfig при создании) |
is_active | bool | Флаг активности пула (может быть приостановлен Team Multisig) |
total_fees_accumulated | u64 | Общие комиссии за всё время (LP + протокольные) |
cumulative_fees_per_share_a | u128 | Q64.64 нарастающая сумма на стороне токена A: cumulative_fees_per_share_a += (fee_lp_a << 64) / total_lp_shares на каждом свопе. O(1) учёт комиссий на холдера |
cumulative_fees_per_share_b | u128 | Q64.64 нарастающая сумма на стороне токена B: cumulative_fees_per_share_b += (fee_lp_b << 64) / total_lp_shares на каждом свопе |
bin_step_bps | u16 | Логарифмический шаг бина (0 для StandardCurve; обычно 10 = 0,1% для MonotonicLadder) |
active_bin_id | i32 | Текущий активный бин (только MonotonicLadder) |
left_anchor_bin | i32 | Верх permanent tail (только MonotonicLadder, иммутабельный) |
permanent_tail_floor_bin | i32 | Низ permanent tail (только MonotonicLadder, иммутабельный) |
last_rebalance_nav_bin | i32 | NAV bin последнего вызова grow_liquidity или compress_liquidity (только MonotonicLadder) |
active_zone_lower | i32 | Нижняя граница текущего active bid wall (только MonotonicLadder) |
ot_treasury_fee_destination | Option<Pubkey> | RWT ATA PDA OT Treasury. При Some взимается дополнительная комиссия 50 bps на свопах и отправляется сюда. None для пулов без OT (например, RWT/USDC). Устанавливается при создании, неизменяемый. |
bump | u8 | Bump seed PDA |
["pool", token_a_mint, token_b_mint]
PoolCreators
Вайтлист кошельков, допущенных к созданию пулов. Макс. 10.| Field | Type | Description |
|---|---|---|
authority | Pubkey | Кто может добавлять/удалять создателей |
creators | [Pubkey; 10] | Кошельки из вайтлиста |
active_count | u8 | Количество активных создателей |
bump | u8 | Bump seed PDA |
["pool_creators"]
LpPosition
На каждую пару (пул, провайдер) для учёта LP. Создаётся при первом добавлении черезinit_if_needed.
| Field | Type | Description |
|---|---|---|
pool | Pubkey | Связанный пул |
owner | Pubkey | Кошелёк LP-провайдера |
shares | u128 | LP-доли во владении |
fees_claimed_per_share_a | u128 | Q64.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_b | u128 | Q64.64 снимок PoolState.cumulative_fees_per_share_b. Pending-комиссии по B: (cumulative_fees_per_share_b − fees_claimed_per_share_b) × shares >> 64 |
last_update_ts | i64 | Метка времени последнего взаимодействия |
bump | u8 | Bump seed PDA |
["lp", pool_state, provider]
BinArray
Бины ликвидности Monotonic Ladder. Один на пул MonotonicLadder.| Field | Type | Description |
|---|---|---|
pool | Pubkey | Связанный пул |
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_id | i32 | ID bins[0] |
bin_step_bps | u16 | Log-шаг цены между бинами (напр. 10 = 0,1%) |
active_bin_id | i32 | Текущий активный бин (двигается только свопами) |
bump | u8 | Bump seed PDA |
["bins", pool_state]
LiquidityNexus
PDA управления LP Areal Finance. Синглтон — один на DEX. Владеет токен-ATA и LP-позициями. Бот-менеджер выполняет операции; средства защищены программой.| Field | Type | Description |
|---|---|---|
manager | Pubkey | Кошелёк бота, который может выполнять операции nexus |
total_deposited_usdc | u64 | Кумулятивный USDC, внесённый через nexus_deposit |
total_deposited_rwt | u64 | Кумулятивный RWT, внесённый через nexus_deposit |
is_active | bool | Флаг активности |
bump | u8 | Bump seed PDA |
["liquidity_nexus"]
owner == nexus.key()) и токен-ATA. Менеджер может свопать/добавлять/удалять LP, но НЕ МОЖЕТ переводить токены из ATA Nexus напрямую — только через инструкции DEX. Вознаграждения LP-комиссий выводятся из YD напрямую в Areal Treasury через nexus_claim_rewards. При компрометации менеджера authority заменяет его через update_nexus_manager.PDA Seeds
| Account | Seeds | Description |
|---|---|---|
| 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 |
authority = pool_state PDA. Создаются как keypair-аккаунты при создании пула.Константы
| Constant | Value | Description |
|---|---|---|
BPS_DENOMINATOR | 10,000 | 100% в базисных пунктах |
DEFAULT_BASE_FEE_BPS | 50 | Комиссия за своп 0,5% |
DEFAULT_LP_FEE_SHARE_BPS | 5,000 | 50% комиссии для LP |
MAX_FEE_BPS | 1,000 | Максимальный лимит комиссии 10% |
MAX_BINS | 1000 | Бинов на пул MonotonicLadder |
GEOMETRIC_R_BPS | 8500 | Коэффициент density для active bid wall (r = 0.85) |
ACTIVE_ZONE_WIDTH | 40 | Бинов в active bid wall |
DEFAULT_PERMANENT_TAIL_OFFSET_BPS | 100 | Permanent tail стоит на initial_NAV − 1% |
DEFAULT_BIN_STEP_BPS | 10 | 0,1% между бинами |
MAX_POOL_CREATORS | 10 | Макс. создателей в вайтлисте |
MIN_LIQUIDITY | 1,000 | Минимум долей при первом добавлении (защита от dust) |
OT_TREASURY_FEE_BPS | 50 | Дополнительная комиссия 0,5% для пар с OT — отправляется на RWT ATA OT Treasury |
RWT_MINT | hardcoded | Минт токена RWT — все пулы должны включать RWT (валидируется в create_pool) |
USDC_MINT | hardcoded | Минт USDC — для валидации токена в nexus_deposit |
OT_PROGRAM_ID | hardcoded | Program ID контракта OT — валидируется в create_pool для проверки владения PDA OT Treasury |
YD_PROGRAM_ID | hardcoded | Валидируется в compound_yield |
События
| Event | Fields | When |
|---|---|---|
DexInitialized | authority, base_fee_bps, timestamp | DEX создан |
PoolCreated | pool, token_a_mint, token_b_mint, pool_type, creator, ot_treasury_fee_destination, timestamp | Пул создан |
LiquidityAdded | pool, provider, amount_a, amount_b, shares_minted, timestamp | LP добавлена |
ZapLiquidityExecuted | pool, provider, input_a, input_b, swapped_amount, shares_minted, timestamp | Zap: автосвоп + добавление LP |
LiquidityRemoved | pool, provider, amount_a, amount_b, shares_burned, timestamp | LP удалена |
LiquidityShifted | pool, rebalancer, old_lower, old_upper, new_lower, new_upper, timestamp | Бины ребалансированы |
SwapExecuted | pool, user, a_to_b, amount_in, amount_out, fee_lp, fee_protocol, fee_ot_treasury, timestamp | Своп выполнен |
LpFeesClaimed | pool, lp_holder, amount, timestamp | LP-холдер вывел вознаграждения за свопы |
CompoundYieldExecuted | pool, rwt_claimed, timestamp | Доход OT автокомпаундирован в пул |
PoolCreatorsUpdated | wallet, action, active_count, timestamp | Вайтлист изменён |
DexConfigUpdated | base_fee_bps, lp_fee_share_bps, rebalancer, is_active, timestamp | Конфигурация изменена |
AuthorityTransferProposed | current_authority, pending_authority, timestamp | Передача предложена |
AuthorityTransferAccepted | old_authority, new_authority, timestamp | Передача принята |
PoolPaused | pool, timestamp | Пул экстренно приостановлен |
PoolUnpaused | pool, timestamp | Пул возобновлён |
NexusInitialized | manager, timestamp | Nexus создан |
NexusDeposited | token_mint, amount, timestamp | Капитал внесён в Nexus |
NexusRewardsClaimed | amount, treasury_destination, timestamp | LP-вознаграждения выведены из YD в Areal Treasury |
NexusProfitsWithdrawn | token_mint, amount, remaining_profit, treasury_destination, timestamp | Прибыль выведена в Areal Treasury |
NexusManagerUpdated | old_manager, new_manager, timestamp | Менеджер Nexus изменён |
Коды ошибок
| Error | Description |
|---|---|
Unauthorized | Не является authority DEX |
CreatorNotWhitelisted | Не в вайтлисте создателей пулов |
DexPaused | Глобальный DEX is_active = false |
PoolNotActive | Пул is_active = false |
WhitelistFull | Макс. 10 создателей |
IdenticalMints | token_a == token_b |
CreatorNotFound | Remove: создатель не в вайтлисте |
ZeroAmount | Amount = 0 |
InsufficientLiquidity | Резервы пула пусты |
InsufficientShares | У LP меньше долей, чем запрошено для сжигания |
InitialLiquidityTooSmall | Первое добавление < MIN_LIQUIDITY |
SlippageExceeded | Выход < min_amount_out |
ZeroOutput | Своп произведёт 0 |
EmptyReserves | Невозможно свопнуть с нулевыми резервами |
MathOverflow | Арифметическое переполнение |
InvalidFee | base_fee_bps > MAX_FEE_BPS |
InvalidFeeShare | lp_fee_share_bps > 10 000 |
InvalidBinRange | lower ≥ upper или вне границ |
BinOutOfRange | ID бина вне BinArray |
InsufficientBinLiquidity | Нет ликвидности в бинах для свопа |
InvalidBinStep | bin_step_bps = 0 для concentrated |
MissingRwtMint | Ни token_a, ни token_b не является RWT_MINT |
InvalidMintOrder | token_a_mint >= token_b_mint (должен быть каноничный порядок) |
InvalidVault | target_vault не является vault_a или vault_b |
NothingToCompound | Не получено RWT из YD |
SelfTransfer | Невозможно передать authority самому себе |
NoPendingAuthority | Нет ожидающей передачи |
InvalidPendingAuthority | Подписант ≠ pending_authority |
InvalidOtTreasuryDestination | ot_treasury_fee_destination не соответствует вычисленному RWT ATA для PDA OT Treasury, или PDA OT Treasury не принадлежит OT_PROGRAM_ID |
MissingOtTreasuryAccount | У пула установлен ot_treasury_fee_destination, но ot_treasury_fee_account не предоставлен в свопе |
InvalidNexusToken | token_mint в nexus_deposit не является USDC_MINT или RWT_MINT |
NexusNotActive | Nexus is_active = false |
InvalidNexusManager | Подписант ≠ nexus.manager |
NexusClaimFailed | PDA Nexus не найден в merkle tree или proof невалиден |
Архитектура и руководство по интеграции
Кросс-программная интеграция
← RWT Engine (свопы хранилища)
← RWT Engine (свопы хранилища)
- RWT Engine
vault_swapделает CPI кnative_dex::swap - PDA RWT Vault подписывает как пользователь
- Менеджер контролирует направление и проскальзывание
← Yield Distribution (конвертация USDC→RWT)
← Yield Distribution (конвертация USDC→RWT)
- YD
convert_to_rwtделает CPI кnative_dex::swap(покупка RWT ниже NAV) - PDA YD Accumulator подписывает как пользователь
Liquidity Nexus (внутренний PDA DEX)
Liquidity Nexus (внутренний PDA DEX)
- 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
→ Yield Distribution (compound_yield)
→ Yield Distribution (compound_yield)
- PDA пула получает доход в RWT от OT из YD через CPI → автокомпаундинг в резервы пула
- Все LP-холдеры получают выгоду пропорционально (более глубокий пул = лучшие сделки)
- LP-комиссии за свопы обрабатываются отдельно через per-side аккумулятор на
PoolState(claim_lp_fees)
← Pool Rebalancer (grow_liquidity / compress_liquidity)
← Pool Rebalancer (grow_liquidity / compress_liquidity)
- Бот 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)
Допущения о доверии
Чеклист развёртывания
Предварительные условия: Yield Distribution должен быть развёрнут (необходим для CPI compound_yield).- Вызвать
initialize_dexс areal_fee_destination (RWT ATA Areal Finance), pause_authority (Team Multisig), rebalancer (кошелёк бота) - Вызвать
initialize_nexusс manager (кошелёк бота Nexus) - Создать ATA Nexus — создать USDC ATA и RWT ATA, принадлежащие PDA Nexus (необходимы до того, как адреса назначения OT и конфигурация RWT Engine будут на них указывать)
- Добавить создателей пулов через
update_pool_creators - Создать пулы — все пулы образуют пару с RWT (например, OT/RWT, RWT/USDC). Минты должны быть в каноничном порядке (
token_a < token_b). Для пар с OT: передать PDAot_treasuryи его RWT ATA — OT Treasury должен быть инициализирован через контракт OT до создания пула - Передать authority в Team Multisig через
propose_authority_transfer+accept_authority_transfer
Сводка потоков токенов
| From | To | Mechanism | Who triggers |
|---|---|---|---|
| Токен пользователя (вход) | Хранилище пула (vault_in) | swap | Пользователь |
| Хранилище пула (vault_out) | Токен пользователя (выход) | swap | Пользователь |
| Комиссия за своп (доля LP) | Остаётся в резервном vault’е пула на стороне начисления; учитывается через cumulative_fees_per_share_<side> на PoolState | swap | Автоматически |
| Резервные 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_liquidity | LP-провайдер |
| LP любое соотношение / один токен | Хранилища пула (через внутренний своп) | zap_liquidity | LP-провайдер |
| Хранилища пула | LP-токены A+B | remove_liquidity | LP-провайдер |
| YD reward vault | Резервы пула (RWT) | compound_yield (CPI к YD) | Crank |
| Доход OT (10% USDC) | USDC ATA crank → USDC ATA Nexus | OT distribute_revenue → nexus_deposit (два шага: crank получает, затем вносит) | Crank |
| Доход RWT Engine (15% RWT) | RWT ATA LiquidityHolding → RWT ATA Nexus | YD 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 |
| Хранилища пула | Токены Nexus | nexus_remove_liquidity | Менеджер Nexus |
| Токен Nexus (USDC или RWT) | ATA Areal Treasury | nexus_withdraw_profits (только разрыв над principal floor) | Authority |
См. также
- Liquidity Nexus — обзор подсистемы, инвариант principal-lock, три уровня доверия и два канала депозита, питающих Nexus
- Контракт Yield Distribution — PDA
LiquidityHoldingиwithdraw_liquidity_holding(атомарный drain RWT-канала с CPI наnexus_record_deposit) - Контракт RWT Engine —
claim_yieldи дележ 70 / 15 / 15, формирующий 15% долю RWT, маршрутизируемую через RWT-канал