Архитектура контракта
Контракт Yield Distribution управляет посекундным начислением наград держателям Ownership Token. Реализует модель наград без стейкинга — держатели зарабатывают награды просто храня OT в кошельках. Контракт работает на для расчёта и зачисления наград в реальном времени, каждую секунду. У каждого держателя создаётся выделенный ClaimAccount (суб-аккаунт), где награды накапливаются — сохраняя историю основного кошелька чистой.Как всё устроено
AREAL DAO регистрирует OT-проект
Команда AREAL вызывает
register_ot_stream, создавая PDA DistributionStream с конфигом стратегии исполнения (параметры DCA, период LP, комиссии). Это одноразовый гейткипер — без него OT не может распределять.Депозит дохода → автоматический триггер
Когда DAO проекта депонирует доход в
RevenueAccount (через deposit_revenue), инструкция distribute_revenue контракта OT срабатывает автоматически — разделяя средства по предварительно настроенным направлениям. Для доли Holders автоматически вызывает create_distribution на этом контракте через CPI. Без повторных голосований — DAO проголосовало один раз при настройке конфига.Создаётся новый изолированный DistributionPool
create_distribution создаёт совершенно новый DistributionPool с уникальным cycle_id. Этот пул полностью независим — свой капитал, своё DCA-развёртывание, своя LP-позиция, свой таймлайн наград. Наследует снапшот конфига stream на момент создания.Модель изоляции гарантирует, что каждое распределение самодостаточно и проверяемо. Распределение 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
register_ot_stream
Регистрирует Ownership Token в системе распределения, создавая PDA
DistributionStream с полным конфигом стратегии исполнения. Вызвать может только команда AREAL DAO — это гейткипер, контролирующий, какие OT-проекты могут распределять доходность через протокол.Пока stream не зарегистрирован, автоматический distribute_revenue контракта OT не сможет переслать средства в этот контракт — самостоятельного подключения нет. Команда AREAL вручную верифицирует проект, настраивает параметры стратегии исполнения и активирует stream.Authority: Engine Authority (команда AREAL DAO)update_stream_config
update_stream_config
Обновляет конфиг стратегии исполнения для существующего
DistributionStream. Команда AREAL DAO может корректировать параметры DCA, длительность развёртывания или период LP при изменении рыночных условий. Изменения применяются только к новым циклам распределения — активные циклы продолжают работать с конфигом, с которым были созданы.Authority: Engine Authority (команда AREAL DAO)deregister_ot_stream
deregister_ot_stream
Удаляет Ownership Token из системы распределения. После отключения контракт OT не может создавать новые циклы распределения. Существующие активные распределения продолжаются до завершения — блокируются только новые.Authority: Engine Authority (команда AREAL DAO)
create_distribution
create_distribution
Вызывается автоматически инструкцией
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)execute_dca_step
execute_dca_step
Выполняет один 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)accrue_rewards
accrue_rewards
Основная инструкция посекундного начисления, исполняемая внутри MagicBlock Ephemeral Rollup crank-сервисом каждую секунду. Для каждого активного
DistributionPool рассчитывает долю награды за текущую секунду и зачисляет в ClaimAccount каждого держателя пропорционально его баланс-секундам в HolderRegistry.Обрабатываются только держатели с активным ClaimAccount. Держатели без суб-аккаунта пропускаются — их баланс OT учитывается в HolderRegistry, но награды не начисляются, пока они не создадут ClaimAccount через init_claim_account.Authority: Permissionless (crank, работает в Ephemeral Rollup)settle_to_l1
settle_to_l1
Периодически выводит накопленную долю наград из LP-позиции на native DEX, конвертирует USDY в RWT и коммитит актуальные балансы
ClaimAccount на Solana L1. Это связывает состояние начислений ER с L1, чтобы держатели могли забрать награды.Authority: Permissionless (crank)update_holder_balance
update_holder_balance
Вызывается при каждом трансфере OT для обновления
HolderRegistry. Фиксирует изменение баланса и обновляет кумулятивные баланс-секунды для отправителя и получателя. Исполняется внутри Ephemeral Rollup для посекундной точности.Authority: Программа токена OT (transfer hook)claim_rewards
claim_rewards
Переводит все накопленные невостребованные RWT-награды из
ClaimAccount держателя в его основной кошелёк. Можно вызвать в любой момент — без lock-up, без минимальной суммы. Это единственная инструкция, которая пишет в кошелёк держателя — всё предыдущее начисление происходит тихо в суб-аккаунте.Authority: Держатель (должен подписать)init_claim_account
init_claim_account
Создаёт единый PDA
ClaimAccount для держателя, который будет получать награды от всех OT-проектов. Должен быть вызван самим держателем (или его мультисигом) — это одноразовый шаг opt-in, активирующий начисление наград по всей экосистеме AREAL.Обычно вызывается при регистрации пользователя на areal.finance при подключении кошелька. После создания держатель начинает получать посекундные награды от каждого OT, которым владеет.Работает как с обычными кошельками, так и с мультисиг-кошельками — PDA ClaimAccount создаётся от адреса держателя независимо от типа кошелька.Authority: Держатель (должен подписать — кошелёк или мультисиг)update_distribution_config
update_distribution_config
Обновляет глобальные параметры распределения. AREAL DAO может изменить период размещения LP по умолчанию (например, с 12 месяцев на 6 или 24), процент комиссии протокола, ссылку на master pool или минимальную сумму распределения.Период размещения определяет, как долго средства распределения остаются в виде LP-позиции в master pool RWT/USDY — зарабатывая swap-комиссии и усиливая доходность перед распределением держателям.Authority: Engine Authority (AREAL DAO governance)
Поток распределения
Депозит дохода срабатывает автоматически
Когда доход поступает в
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.Стратегия исполнения Stream
КаждыйDistributionStream имеет собственную стратегию исполнения, контролирующую как средства поступают в master pool. Это предотвращает ценовые шоки — если крупный проект вносит $10M дохода, мгновенная покупка всего RWT взвинтит цену и нарушит рынок.
Проблема
Как работает DCA-развёртывание
Вместо мгновенного размещения средств в LP-пуле контракт исполняет DCA-стратегию (усреднение стоимости) — покупая RWT и USDY малыми порциями в течение настроенного периода развёртывания.Средства получены
create_distribution получает сумму дохода и сохраняет в DistributionPool. Никакой рыночной активности пока нет.Фаза DCA-развёртывания
Crank вызывает
execute_dca_step с настроенным интервалом (напр., каждые 60 секунд). Каждый шаг покупает малую порцию RWT и USDY на native DEX и добавляет в LP-позицию. Размер шага ограничен max_single_trade_bps.Развёртывание завершено
По истечении
dca_deployment_days (напр., 30 дней) все средства размещены как LP-позиция в master pool RWT/USDY.NAV-Aware приобретение
Каждый DCA-шаг не покупает вслепую — он сравнивает рыночную цену RWT с NAV Book Value и выбирает оптимальный путь приобретения:Этот механизм создаёт автоматический стабилизатор: когда 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 и гарантирует, что держатели осознанно вступают в систему.