Native DEX
✅ Готово к разработке
compound_yield.
Ключевые концепции
22 инструкции
6 аккаунтов состояния
6 PDA Seeds
Два типа пулов
StandardCurve
- Простая, проверенная математика AMM
- Непрерывное ценообразование:
price = reserve_b / reserve_a - LP-доли:
sqrt(a × b)при первом добавлении, пропорционально после - Подходит для: общей торговли, глубокой ликвидности
Concentrated
- 70 бинов на пул, настраиваемый размер шага (по умолчанию 0,1%)
- Бины ниже активного = только USDC (bid). Бины выше = только RWT (ask). Активный бин = оба.
- Пользователи добавляют/удаляют ликвидность так же, как в StandardCurve — бинами управляет Rebalancer
- Своп проходит через бины, потребляя ликвидность и сдвигая активный бин
shift_liquidity(только Rebalancer) перепозиционирует диапазон вокруг NAV- Подходит для: узких диапазонов вокруг NAV, эффективности капитала
Архитектура комиссий
Базовая комиссия
Распределение комиссии
- LP-комиссия (по умолчанию 50% от базовой) → в RWT, переводится в fee vault пула. LP-холдеры забирают вознаграждения через
claim_lp_fees— мгновенно, без vesting. - Протокольная комиссия (по умолчанию 50% от базовой) → всегда в RWT, переводится на
areal_fee_destination(RWT ATA Areal Finance).
Комиссии поверх свопа (без размывания пула)
amount_in + fees (дополнительные RWT сверху). Если пользователь покупает RWT: пользователь получает amount_out полностью, комиссии берутся отдельно из валового выхода RWT перед доставкой.Всегда в RWT
LP-комиссия → Fee Vault (мгновенный вывод)
claim_lp_fees — без вестинга, без merkle proof, без офф-чейн сервера. Доступная сумма = fee_vault_balance × lp_shares / total_shares - already_claimed.Комиссия казначейства OT (только для пар с OT)
OT_TREASURY_FEE_BPS = 50 — изменяется только через обновление программы.Шесть ролей
🏛️ Authority
update_dex_config— комиссии, адреса назначения, rebalancerupdate_pool_creators— управление вайтлистомpropose_authority_transfer
🛑 Pause Authority
pause_pool/unpause_pool
⚖️ Rebalancer
shift_liquidity— управление концентрацией бинов
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 — может вызывать shift_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 активного бина |
create_pool, плюс:bin_array(init) — PDA seed:["bins", pool_state]
bin_step_bps > 0- Один из
token_a_mintилиtoken_b_mintдолжен бытьRWT_MINT token_a_mint < token_b_mint(каноничный порядок)- Создатель должен быть в вайтлисте
- Валидация OT treasury аналогична
create_pool(опциональные аккаунты, те же проверки деривации)
target_bin_count бинов (по умолчанию 40) активно используются Rebalancer — остальные бины служат буфером для сдвига диапазона при изменении NAV.Ликвидность
add_liquidity и remove_liquidity независимо от типа пула. Для пулов Concentrated протокол (Pool Rebalancer) управляет концентрацией бинов внутренне через shift_liquidity. Пользователи изолированы от управления бинами — они просто вносят токены и получают пропорциональные доли.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.shift_liquidity) только сдвигает весь диапазон при изменении NAV — он не обрабатывает новые депозиты. Это гарантирует, что свопы всегда имеют доступ ко всей внесённой ликвидности.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-доли.»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.shift_liquidity
shift_liquidity
add_liquidity распределяет по бинам немедленно.| Parameter | Type | Description |
|---|---|---|
nav_bin | i32 | Целевой ID бина NAV (центр нового диапазона). Рассчитывается офф-чейн ботом Rebalancer из rwt_vault.nav_book_value через RPC: nav_bin = log(nav_price) / log(1 + bin_step_bps/10000) |
target_bin_count | u16 | Общее количество бинов в диапазоне (разделено вокруг nav_bin) |
dex_config.rebalancer)Аккаунты:rebalancer(signer) — должен соответствоватьdex_config.rebalancerdex_config— для валидации rebalancerpool_state(mut) — должен быть типа Concentratedbin_array(mut)
- Пул должен быть типа Concentrated
target_bin_count > 0target_bin_count ≤ MAX_BINS(70)- Вычисленный диапазон
[nav_bin - count/2, nav_bin + count/2]в пределах BinArray - Новый диапазон должен отличаться от текущего (предотвращает бесполезную ребалансировку, тратящую вычислительные ресурсы)
- Вычисление целей: Для каждого бина в диапазоне рассчитывается целевая ликвидность по формуле пирамиды (асимметричная 2:1, центрированная на nav_bin).
- Бины на стороне bid (ниже nav_bin): только USDC, взвешены по пирамиде
target_usdc(bin) = total_pool_usdc * 2/3 * weight(bin) / total_bid_weightweight(bin) = bin - lower + 1(ближе к NAV = больше) nav_bin: и RWT, и USDC (вершина пирамиды)- Бины на стороне ask (выше nav_bin): только RWT, взвешены по пирамиде
target_rwt(bin) = total_pool_rwt * 1/3 * weight(bin) / total_ask_weightweight(bin) = upper - bin + 1(ближе к NAV = больше)
- Бины на стороне bid (ниже nav_bin): только USDC, взвешены по пирамиде
- Вычисление дельт и ребалансировка:
active_bin_id НЕ изменяется — только свопы перемещают активный бин. Сдвиг только перепозиционирует, где находится ликвидность.Без разрыва ликвидности: В отличие от «собрать всё → перераспределить», бины всегда содержат ликвидность во время сдвига. Токены перетекают напрямую из избыточных бинов в дефицитные.Пирамидальное распределение:mint_rwt (дешевле по цене NAV).Эффект: Токены не входят/выходят из хранилищ. Dust от целочисленного деления остаётся в резервах. Эмитирует LiquidityShifted.Своп
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→ RWT переводится вfee_vaultпула (RWT-аккаунт каждого пула, доступен 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(без дополнительной комиссии)
amount_in > 0- Резервы пула ненулевые:
reserve_in > 0 && reserve_out > 0(StandardCurve) или хотя бы один бин имеет ликвидность (Concentrated). ОшибкаEmptyReserves, если в пуле нет ликвидности. amount_out ≥ min_amount_out(проверка проскальзывания)amount_out > 0- Пул и DEX должны быть активны
SwapExecuted.Компаундинг доходности
compound_yield
compound_yield
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_shareslp_position(mut) — читаетshares, обновляетfees_claimedfee_vault(mut) — RWT fee vault пула, ограничение:owner == pool_state.key()lp_holder_rwt_ata(mut) — RWT ATA холдера, получает выведенные комиссииtoken_program
lp_position.owner == lp_holder.key()claimable > 0fee_vault.amount >= claimable
LpFeesClaimed.pool_state.cumulative_fees_per_share += fee_lp * PRECISION / total_lp_shares. Это позволяет вести учёт O(1) для каждого холдера — итерация по LP-холдерам не нужна. Каждая LP-позиция отслеживает свои fees_claimed для предотвращения двойного вывода. Паттерн используется в Sushiswap MasterChef, Raydium и большинстве DeFi-распределителей комиссий.Liquidity Nexus
Управление LP Areal Finance. PDA Nexus владеет LP-позициями и токен-аккаунтами. Бот-менеджер выполняет операции; средства защищены программой (менеджер не может извлечь токены напрямую). Вознаграждения LP-комиссий накапливаются в fee vault пула и выводятся в 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). Затем crank вызывает nexus_deposit для направления в Nexus. То же для RWT из claim_yield — crank получает 15% RWT, затем вызывает nexus_deposit.nexus_claim_rewards
nexus_claim_rewards
| Parameter | Type | Description |
|---|---|---|
pool | Pubkey | Пул для вывода комиссий |
authority(signer) — должен соответствоватьdex_config.authoritydex_config— для валидации authoritynexus— PDA, подписывает как LP-холдерpool_state— читаетcumulative_fees_per_sharenexus_lp_position(mut) — LP-позиция Nexus в этом пуле, обновляетfees_claimedfee_vault(mut) — RWT fee vault пулаtreasury_token_account(mut) — RWT ATA Areal Treasury, получает выведенные вознагражденияtoken_program
- Расчёт доступного для вывода по той же формуле, что и
claim_lp_fees - Перевод RWT из fee_vault →
treasury_token_account(Areal Treasury) - Обновление
nexus_lp_position.fees_claimed - Эмитирует
NexusRewardsClaimed
cumulative_fees_per_share, что и для обычных LP-выводов. LP-позиции PDA Nexus зарабатывают комиссии как любой другой LP-холдер — вознаграждения идут напрямую в Areal Treasury.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.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 — единственный подписант, допущенный к вызову shift_liquidity |
is_active | bool | Глобальный аварийный выключатель DEX |
bump | u8 | Bump seed PDA |
["dex_config"]
PoolState
Один на пару токенов. Хранит резервы, доли, комиссии.| Field | Type | Description |
|---|---|---|
pool_type | PoolType | StandardCurve или Concentrated |
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 + протокольные) |
fee_vault | Pubkey | RWT-аккаунт для сбора LP-комиссий (authority = PDA пула) |
cumulative_fees_per_share | u128 | Нарастающая сумма fee_lp * PRECISION / total_lp_shares — используется для учёта комиссий O(1) на холдера |
bin_step_bps | u16 | Шаг бина (0 для StandardCurve) |
active_bin_id | i32 | Текущий активный бин (только Concentrated) |
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 | u128 | Кумулятивные выведенные LP-комиссии (предотвращает двойной вывод) |
fee_debt | u128 | Снимок комиссий на момент депозита — исключает комиссии до депозита из вывода |
last_update_ts | i64 | Метка времени последнего взаимодействия |
bump | u8 | Bump seed PDA |
["lp", pool_state, provider]
BinArray
Бины концентрированной ликвидности. Один на пул Concentrated.| Field | Type | Description |
|---|---|---|
pool | Pubkey | Связанный пул |
bins | [Bin; 70] | Массив бинов. Каждый бин: liquidity_a: u64 (RWT), liquidity_b: u64 (USDC). Ниже активного: только liquidity_b. Выше активного: только liquidity_a. Активный бин: оба. |
lower_bin_id | i32 | ID bins[0] |
bin_step_bps | u16 | Шаг цены между бинами |
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 | 70 | Бинов на пул концентрированной ликвидности |
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-комиссии за свопы обрабатываются отдельно через fee vault каждого пула (
claim_lp_fees)
← Pool Rebalancer (shift_liquidity)
← Pool Rebalancer (shift_liquidity)
- Бот Rebalancer вызывает
shift_liquidityдля перепозиционирования концентрированной LP вокруг NAV - Кошелёк 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) | Fee vault пула (RWT) | swap | Автоматически |
| Fee vault пула | RWT ATA LP-холдера | claim_lp_fees | 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 crank → RWT ATA Nexus | RWT claim_yield → nexus_deposit (два шага: crank получает, затем вносит) | Crank |
| LP-вознаграждения Nexus (RWT) | RWT ATA Areal Treasury | nexus_claim_rewards | Authority |
| Токены Nexus | Хранилища пула | nexus_add_liquidity | Менеджер Nexus |
| Хранилища пула | Токены Nexus | nexus_remove_liquidity | Менеджер Nexus |