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

Архитектура контракта

Контракт Yield Distribution управляет посекундным начислением наград держателям Ownership Token. Реализует модель наград без стейкинга — держатели зарабатывают награды просто храня OT в кошельках. Контракт работает на для расчёта и зачисления наград в реальном времени, каждую секунду. У каждого держателя создаётся выделенный ClaimAccount (суб-аккаунт), где награды накапливаются — сохраняя историю основного кошелька чистой.

Как всё устроено

1

AREAL DAO регистрирует OT-проект

Команда AREAL вызывает register_ot_stream, создавая PDA DistributionStream с конфигом стратегии исполнения (параметры DCA, период LP, комиссии). Это одноразовый гейткипер — без него OT не может распределять.
2

Депозит дохода → автоматический триггер

Когда DAO проекта депонирует доход в RevenueAccount (через deposit_revenue), инструкция distribute_revenue контракта OT срабатывает автоматически — разделяя средства по предварительно настроенным направлениям. Для доли Holders автоматически вызывает create_distribution на этом контракте через CPI. Без повторных голосований — DAO проголосовало один раз при настройке конфига.
3

Создаётся новый изолированный DistributionPool

create_distribution создаёт совершенно новый DistributionPool с уникальным cycle_id. Этот пул полностью независим — свой капитал, своё DCA-развёртывание, своя LP-позиция, свой таймлайн наград. Наследует снапшот конфига stream на момент создания.
4

Несколько пулов работают параллельно

Если DAO проекта OT отправит средства снова в следующем квартале — создастся ещё один пул. Все пулы работают независимо — каждый на своей стадии DCA-развёртывания, усиления доходности или начисления наград. Они никогда не сливаются.
Модель изоляции гарантирует, что каждое распределение самодостаточно и проверяемо. Распределение 500Kвянвареи500K в январе и 300K в апреле — у каждого своя LP-позиция, своя DCA-история и свой график наград — полностью прослеживаемо от депозита до claim.

On-Chain аккаунты

DistributionPool

Представляет один изолированный цикл распределения. Каждый раз, когда DAO проекта OT отправляет средства, создаётся новый DistributionPool со своим DCA-развёртыванием, LP-позицией и начислением наград — полностью независимый от предыдущих циклов. Несколько пулов могут работать параллельно для одного OT. Seeds: ["dist_pool", ot_mint, cycle_id].

HolderRegistry

Отслеживает балансы и длительность удержания OT для посекундного расчёта наград. Обновляется при каждом трансфере OT через transfer hook. Хранит кумулятивные баланс-секунды для каждого держателя. Делегирован в MagicBlock ER. Seeds: ["holder_registry", ot_mint].

ClaimAccount

Суб-аккаунт держателя — единый PDA на держателя, аккумулирующий награды от всех OT-проектов. Создаётся один раз через init_claim_account с оплатой rent в SOL, затем используется для всех Ownership Tokens держателя. Только держатели с активным ClaimAccount получают награды — нет аккаунта, нет начислений. Seeds: ["claim", holder].

DistributionStream

PDA конфигурации для каждого OT — привязывает Ownership Token к системе распределения с собственным конфигом стратегии исполнения. Создаётся вручную командой AREAL DAO через register_ot_stream. Хранит ссылку на OT mint, период размещения LP, переопределение комиссии и полную стратегию исполнения (интервал DCA, макс. размер сделки, длительность развёртывания), контролирующую как средства поступают в master pool. Seeds: ["stream", ot_mint].

DistributionConfig

Глобальная конфигурация, управляемая AREAL DAO. Хранит период размещения LP по умолчанию (изначально 12 месяцев, настраивается DAO), комиссию протокола AREAL (0.25%), ссылку на master pool (RWT/USDY) и минимальную сумму распределения. Все параметры обновляются через update_distribution_config. Seeds: ["dist_config"].

ClaimAccount — суб-аккаунт держателя

Чтобы начать получать награды, держатель должен явно создать свой ClaimAccount один раз, вызвав init_claim_account и оплатив rent в SOL. Обычно это происходит при регистрации пользователя на areal.finance при подключении кошелька. Один ClaimAccount на держателя — для всех OT-проектов. Один и тот же суб-аккаунт получает награды от каждого Ownership Token, которым владеет держатель. Не нужно создавать отдельные аккаунты для каждого проекта. Нет ClaimAccount = нет наград. Инструкция accrue_rewards обрабатывает только держателей с активным ClaimAccount. Держатели OT без суб-аккаунта просто пропускаются — их доля не теряется, но не начисляется, пока аккаунт не создан. Эта модель работает как для обычных кошельков, так и для мультисиг-кошельков — любой адрес Solana может создать ClaimAccount.

Один аккаунт, все проекты

Единый ClaimAccount накапливает награды от каждого OT держателя. Купите 5 разных OT — все награды поступают в одно место. Создайте один раз, используйте всегда.

Чистая история кошелька

Посекундные зачисления происходят внутри ClaimAccount — основной кошелёк держателя не видит никаких транзакций до явного claim.

Поддержка мультисигов

Мультисиг-кошельки могут создавать ClaimAccount и забирать награды так же, как обычные кошельки. PDA ClaimAccount создаётся от адреса мультисига.
Rent за ClaimAccount — стандартный rent Solana (~0.002 SOL). Это одноразовая стоимость — один ClaimAccount покрывает все OT-проекты и все циклы распределения, навсегда.

Основные инструкции

register_ot_stream

Регистрирует Ownership Token в системе распределения, создавая PDA DistributionStream с полным конфигом стратегии исполнения. Вызвать может только команда AREAL DAO — это гейткипер, контролирующий, какие OT-проекты могут распределять доходность через протокол.Пока stream не зарегистрирован, автоматический distribute_revenue контракта OT не сможет переслать средства в этот контракт — самостоятельного подключения нет. Команда AREAL вручную верифицирует проект, настраивает параметры стратегии исполнения и активирует stream.Authority: Engine Authority (команда AREAL DAO)
Accounts:
  - engine_authority:    Signer (сотрудник команды AREAL DAO)
  - distribution_stream: PDA (init)
  - ot_mint:             Регистрируемый OT mint
  - distribution_config: PDA
  - system_program

Args:
  - lp_period_days: u32              Период размещения LP (напр., 365)
  - protocol_fee_bps: Option<u16>    Переопределение комиссии протокола (null = глобальный дефолт)
  - dca_interval_secs: u32           Интервал между DCA-покупками в секундах (напр., 60 = каждую минуту)
  - dca_deployment_days: u32         Длительность фазы развёртывания средств в LP (напр., 30 дней)
  - max_single_trade_bps: u16        Макс. размер одной DCA-сделки в bps от общей суммы (напр., 100 = 1%)
Обновляет конфиг стратегии исполнения для существующего DistributionStream. Команда AREAL DAO может корректировать параметры DCA, длительность развёртывания или период LP при изменении рыночных условий. Изменения применяются только к новым циклам распределения — активные циклы продолжают работать с конфигом, с которым были созданы.Authority: Engine Authority (команда AREAL DAO)
Accounts:
  - engine_authority:    Signer (сотрудник команды AREAL DAO)
  - distribution_stream: PDA (mut)

Args:
  - lp_period_days: Option<u32>
  - dca_interval_secs: Option<u32>
  - dca_deployment_days: Option<u32>
  - max_single_trade_bps: Option<u16>
Удаляет Ownership Token из системы распределения. После отключения контракт OT не может создавать новые циклы распределения. Существующие активные распределения продолжаются до завершения — блокируются только новые.Authority: Engine Authority (команда AREAL DAO)
Accounts:
  - engine_authority:    Signer (сотрудник команды AREAL DAO)
  - distribution_stream: PDA (mut, close)
  - ot_mint:             Отключаемый OT mint
Вызывается автоматически инструкцией distribute_revenue контракта OT через CPI при обработке направления Holders. Отдельное голосование на стороне Yield Distribution не требуется — DAO проекта OT голосует один раз за distribute_revenue, и эта инструкция срабатывает автоматически для доли холдеров.Каждый вызов создаёт новый изолированный DistributionPool — полностью независимый цикл распределения со своим DCA-развёртыванием, своей LP-позицией и своим таймлайном наград. Если DAO проекта OT распределяет выручку 3 раза — создаются 3 отдельных пула, каждый работающий параллельно.Требует зарегистрированный DistributionStream — если OT не зарегистрирован командой AREAL, инструкция отклоняется.Authority: Контракт OT (автоматический CPI-вызов из distribute_revenue)
Accounts:
  - ot_program:          Вызывающая программа OT
  - ot_config:           PDA конфига OT (верификация)
  - distribution_stream: PDA (верификация + источник конфига)
  - distribution_pool:   PDA (init)
  - distribution_config: PDA
  - rwt_mint
  - usdy_mint
  - token_program

Args:
  - amount: u64          Общая сумма распределения (после комиссии AREAL)
Выполняет один NAV-aware DCA-шаг. Перед каждым шагом контракт считывает текущую рыночную цену RWT с native DEX и NAV Book Value из RWT Vault, затем определяет оптимальную стратегию приобретения:
  • Market ≥ NAV (на уровне или выше пега): минтит RWT по цене NAV Book Value (дешевле рынка) + покупает USDY за вторую половину → размещает в LP
  • Market < NAV (ниже пега): покупает RWT с рынка native DEX (дешевле минтинга, плюс поддерживает цену к NAV). Чем глубже скидка, тем больше доля RWT — вплоть до ~100% при глубоком дисконте
Размер шага ограничен max_single_trade_bps. Развёртывание продолжается до полного размещения средств или истечения dca_deployment_days.Authority: Permissionless (crank, работает в Ephemeral Rollup)
Accounts:
  - crank:               Signer
  - distribution_pool:   PDA (mut)
  - distribution_stream: PDA (источник конфига)
  - rwt_vault:           PDA (источник NAV Book Value)
  - rwt_mint:            Mint RWT (для пути минтинга)
  - master_pool:         Пул RWT/USDY на native DEX (mut, CPI)
  - lp_position:         Аккаунт LP-позиции (mut)
  - oracle_price_feed:   Оракл MagicBlock (рыночная цена)
  - dex_program
  - token_program
Основная инструкция посекундного начисления, исполняемая внутри MagicBlock Ephemeral Rollup crank-сервисом каждую секунду. Для каждого активного DistributionPool рассчитывает долю награды за текущую секунду и зачисляет в ClaimAccount каждого держателя пропорционально его баланс-секундам в HolderRegistry.Обрабатываются только держатели с активным ClaimAccount. Держатели без суб-аккаунта пропускаются — их баланс OT учитывается в HolderRegistry, но награды не начисляются, пока они не создадут ClaimAccount через init_claim_account.Authority: Permissionless (crank, работает в Ephemeral Rollup)
Accounts:
  - crank:               Signer
  - distribution_pool:   PDA (mut)
  - holder_registry:     PDA (делегирован в ER)
  - claim_accounts:      Только активные ClaimAccount PDA (mut)
Периодически выводит накопленную долю наград из LP-позиции на native DEX, конвертирует USDY в RWT и коммитит актуальные балансы ClaimAccount на Solana L1. Это связывает состояние начислений ER с L1, чтобы держатели могли забрать награды.Authority: Permissionless (crank)
Accounts:
  - crank:               Signer
  - distribution_pool:   PDA (mut)
  - master_pool:         Пул RWT/USDY (mut, CPI)
  - lp_position:         Аккаунт LP-позиции (mut, CPI)
  - dex_program
  - token_program
Вызывается при каждом трансфере OT для обновления HolderRegistry. Фиксирует изменение баланса и обновляет кумулятивные баланс-секунды для отправителя и получателя. Исполняется внутри Ephemeral Rollup для посекундной точности.Authority: Программа токена OT (transfer hook)
Accounts:
  - ot_mint:             Mint OT (верификация)
  - holder_registry:     PDA (mut, делегирован в ER)
  - from_wallet:         Адрес отправителя
  - to_wallet:           Адрес получателя

Args:
  - from_prev_balance: u64
  - to_prev_balance: u64
  - amount: u64
Переводит все накопленные невостребованные RWT-награды из ClaimAccount держателя в его основной кошелёк. Можно вызвать в любой момент — без lock-up, без минимальной суммы. Это единственная инструкция, которая пишет в кошелёк держателя — всё предыдущее начисление происходит тихо в суб-аккаунте.Authority: Держатель (должен подписать)
Accounts:
  - holder:              Signer
  - claim_account:       PDA (mut)
  - holder_rwt_ata:      Токен-аккаунт RWT держателя (mut)
  - rwt_reserve:         RWT-резерв распределения (mut)
  - token_program
Создаёт единый PDA ClaimAccount для держателя, который будет получать награды от всех OT-проектов. Должен быть вызван самим держателем (или его мультисигом) — это одноразовый шаг opt-in, активирующий начисление наград по всей экосистеме AREAL.Обычно вызывается при регистрации пользователя на areal.finance при подключении кошелька. После создания держатель начинает получать посекундные награды от каждого OT, которым владеет.Работает как с обычными кошельками, так и с мультисиг-кошельками — PDA ClaimAccount создаётся от адреса держателя независимо от типа кошелька.Authority: Держатель (должен подписать — кошелёк или мультисиг)
Accounts:
  - holder:              Signer (платит rent в SOL, кошелёк или мультисиг)
  - claim_account:       PDA (init)
  - system_program
Обновляет глобальные параметры распределения. AREAL DAO может изменить период размещения LP по умолчанию (например, с 12 месяцев на 6 или 24), процент комиссии протокола, ссылку на master pool или минимальную сумму распределения.Период размещения определяет, как долго средства распределения остаются в виде LP-позиции в master pool RWT/USDY — зарабатывая swap-комиссии и усиливая доходность перед распределением держателям.Authority: Engine Authority (AREAL DAO governance)
Accounts:
  - engine_authority:    Signer (текущий engine authority)
  - distribution_config: PDA (mut)

Args:
  - default_period_days: Option<u32>   Период размещения LP в днях (по умолчанию 365)
  - protocol_fee_bps: Option<u16>      Комиссия протокола AREAL (по умолчанию 25 = 0.25%)
  - min_distribution: Option<u64>      Минимальная сумма распределения в USDC

Поток распределения

Депозит дохода срабатывает автоматически

Когда доход поступает в RevenueAccount OT, distribute_revenue срабатывает автоматически. Для доли Holders контракт OT вызывает create_distribution через CPI — создаётся новый изолированный DistributionPool. Голосование за каждое распределение не нужно — DAO настроило сплит один раз. Каждый депозит = новый независимый цикл.

Фаза DCA-развёртывания

Средства не размещаются мгновенно. Crank выполняет execute_dca_step с настроенным в stream интервалом (напр., каждые 60 секунд), постепенно покупая RWT и USDY на native DEX и формируя LP-позицию в течение периода развёртывания (напр., 30 дней). Это предотвращает ценовые шоки на RWT.

Фаза усиления доходности

После завершения развёртывания полная LP-позиция зарабатывает дополнительную доходность из трёх источников: swap-комиссии, рост USDY и рост NAV Book Value RWT. Держатели получают больше первоначальной суммы.

Держатель создаёт ClaimAccount

Держатель OT создаёт свой ClaimAccount через init_claim_account (обычно при регистрации на areal.finance), оплачивая rent в SOL. Это активирует начисление наград для его адреса. Работает для обычных кошельков и мультисигов.

Посекундное начисление в ER

Каждую секунду accrue_rewards исполняется внутри MagicBlock Ephemeral Rollup. Обрабатывает только держателей с активным ClaimAccount, рассчитывает долю каждого по баланс-секундам и зачисляет в ClaimAccount — тихо, не касаясь основного кошелька.

Периодический settlement на L1

settle_to_l1 периодически выводит из LP-позиции, конвертирует USDY в RWT и коммитит актуальные балансы ClaimAccount на Solana L1.

Получение наград

Держатели вызывают claim_rewards в любое время для перевода всех накопленных RWT из ClaimAccount в кошелёк. Одна транзакция, все награды, чистая история.

Стратегия исполнения Stream

Каждый DistributionStream имеет собственную стратегию исполнения, контролирующую как средства поступают в master pool. Это предотвращает ценовые шоки — если крупный проект вносит $10M дохода, мгновенная покупка всего RWT взвинтит цену и нарушит рынок.

Проблема

Как работает DCA-развёртывание

Вместо мгновенного размещения средств в LP-пуле контракт исполняет DCA-стратегию (усреднение стоимости) — покупая RWT и USDY малыми порциями в течение настроенного периода развёртывания.
1

Средства получены

create_distribution получает сумму дохода и сохраняет в DistributionPool. Никакой рыночной активности пока нет.
2

Фаза DCA-развёртывания

Crank вызывает execute_dca_step с настроенным интервалом (напр., каждые 60 секунд). Каждый шаг покупает малую порцию RWT и USDY на native DEX и добавляет в LP-позицию. Размер шага ограничен max_single_trade_bps.
3

Развёртывание завершено

По истечении dca_deployment_days (напр., 30 дней) все средства размещены как LP-позиция в master pool RWT/USDY.
4

Фаза усиления доходности

LP-позиция зарабатывает swap-комиссии и рост токенов оставшуюся часть lp_period_days (напр., 12 месяцев всего минус 30 дней развёртывания = 11 месяцев чистой доходности).
Каждый DCA-шаг не покупает вслепую — он сравнивает рыночную цену RWT с NAV Book Value и выбирает оптимальный путь приобретения:
RWT торгуется на уровне или выше справедливой стоимости. Минтить дешевле, чем покупать с рынка.
  • 50% суммы шага → минт RWT по цене NAV Book Value через контракт RWT
  • 50%покупка USDY
  • Оба актива размещаются в LP-позиции master pool
Это стандартный сплит 50/50. Минтинг по NAV гарантирует, что контракт никогда не переплачивает.
Этот механизм создаёт автоматический стабилизатор: когда RWT торгуется ниже NAV, потоки распределения смещаются к покупке RWT с рынка — обеспечивая органическое давление покупки, возвращающее цену к справедливой стоимости. Чем глубже скидка, тем сильнее поддержка.

Параметры конфига stream

ПараметрОписаниеПример
dca_interval_secsИнтервал между DCA-шагами60 (каждую минуту)
dca_deployment_daysОбщая длительность фазы развёртывания30 дней
max_single_trade_bpsМакс. размер одной DCA-сделки в bps от общей суммы100 (1% за сделку)
lp_period_daysОбщий период размещения LP (включая фазу DCA)365 дней
Команда AREAL DAO настраивает эти параметры для каждого stream на основе размера проекта и рыночных условий. Небольшой проект может развернуться за 7 дней с крупными шагами, а крупный — за 60 дней с мелкими посекундными шагами. Конфиг обновляется через update_stream_config для будущих циклов.

Интеграция с MagicBlock

Контракт Yield Distribution исполняет основную логику начисления на для посекундной гранулярности.

Делегированные аккаунты

АккаунтЗачем делегирован
HolderRegistryБаланс-секунды должны обновляться при каждом трансфере OT в реальном времени
ClaimAccount (все)Награды зачисляются каждую секунду — требуется запись в ER для непрерывного начисления
DistributionPoolОстаток уменьшается каждую секунду — отслеживает прогресс распределения в реальном времени

Модель исполнения

Посекундное начисление

accrue_rewards запускается каждую секунду внутри ER. ClaimAccount каждого держателя получает пропорциональную долю награды за текущую секунду — по баланс-секундам.

Transfer hooks в ER

Трансферы OT вызывают update_holder_balance внутри ER, гарантируя, что снапшоты баланс-секунд фиксируются в точную секунду перевода.

Settlement на L1

settle_to_l1 коммитит балансы ClaimAccount на L1, выводит из LP-позиции и конвертирует USDY → RWT. После settlement держатели могут забрать награды на L1.
claim_rewards исполняется на Solana L1 — считывает последний закоммиченный баланс ClaimAccount и переводит RWT в кошелёк держателя. Держателю никогда не нужно взаимодействовать с Ephemeral Rollup напрямую.

Посекундное начисление

HolderRegistry отслеживает кумулятивные баланс-секунды каждого держателя — интеграл их баланса OT по времени. Это гарантирует, что награды распределяются пропорционально как количеству удерживаемых токенов, так и длительности удержания.
holder_share = holder_balance_seconds / total_balance_seconds
Каждый трансфер OT вызывает update_holder_balance, который фиксирует текущие баланс-секунды перед обновлением. Каждую секунду accrue_rewards считывает эти снапшоты и зачисляет в каждый ClaimAccount пропорционально.
Эта модель означает, что держатель 1 000 OT в течение 30 дней зарабатывает ровно вдвое больше, чем тот, кто держит 1 000 OT 15 дней — точно, справедливо и непрерывно.

Безопасность

Контролируемое подключение

Только OT, зарегистрированные командой AREAL DAO (через register_ot_stream), могут создавать распределения. Нет самостоятельного подключения — команда верифицирует каждый проект перед активацией потоков доходности.

Создание только через CPI

Пулы распределения могут создаваться только через CPI из верифицированного контракта OT с зарегистрированным DistributionStream. Внешний вызывающий не может создать мошеннический цикл распределения.

Пропорциональная справедливость

Отслеживание баланс-секунд гарантирует, что поздние покупатели получают пропорционально меньше, чем долгосрочные держатели. Нет эксплойтов через flash-loan или тайминг.

Изоляция суб-аккаунтов

ClaimAccount каждого держателя — отдельный PDA. Claim одного держателя не может повлиять на баланс другого. Основной кошелёк держателя не записывается, пока он явно не сделает claim.

Permissionless crank

accrue_rewards и settle_to_l1 — permissionless: кто угодно может запустить crank. Распределение продолжается независимо от того, кто его триггерит.

Консистентность ER → L1

Балансы ClaimAccount только увеличиваются (append-only). Settlement на L1 коммитит монотонно растущий баланс — нет риска двойного claim или отката баланса.

Чистая история кошелька

Посекундное начисление происходит в PDA ClaimAccount, а не в кошельке держателя. Ноль транзакционного спама — история кошелька остаётся чистой, пока держатель не решит забрать награды.

Opt-in начисление

Только держатели, явно создавшие ClaimAccount, получают награды. Это отфильтровывает мёртвые кошельки, снижает нагрузку crank и гарантирует, что держатели осознанно вступают в систему.
Контракт Yield Distribution в настоящее время находится в разработке. Эта документация описывает целевую архитектуру. Код контракта ещё не прошёл аудит.