13 KiB
Программа shine_payments
Документ описывает текущее целевое поведение программы shine_payments.
Текущая целевая реализация программы:
- без Anchor;
- без использования вспомогательной зависимости из
programs/common; - на чистом
solana_programс ручным разбором инструкций, PDA и состояний.
Назначение программы:
- хранить общие настройки экономической модели платежей и выплат;
- принимать покупку тикетов очереди выплат;
- хранить три очереди и отдельные ticket PDA;
- выдавать лимиты менеджерам на ручное добавление тикетов;
- выполнять пошаговые выплаты из inflow-вольта.
Если код и этот документ расходятся, нужно в том же изменении синхронизировать либо код, либо документ.
1. Program ID
Текущий program id:
m48pWRGWrMj3TEHjuU4zsp5Gju4e7ZaPovk8RcVt7kR
2. Основная модель
shine_payments разделяет две вещи:
- регистрацию долга/обязательств через тикеты;
- фактическую выплату через отдельный inflow-вольт.
Это важно:
- покупка тикета не означает немедленную выплату из вольта;
- payout выполняется отдельной инструкцией
step_payout; - inflow-вольт должен быть заранее или отдельно пополнен.
3. PDA и seed-правила
3.1. Single PDA
- config:
shine_payments_config - coef/limit:
shine_payments_coef_limit - queues:
shine_payments_queues - inflow vault:
shine_payments_inflow_vault
3.2. Ticket PDA
- queue 1 seed prefix:
shine_payments_q1_ticket - queue 2 seed prefix:
shine_payments_q2_ticket - queue 3 seed prefix:
shine_payments_q3_ticket - второй seed:
ticket_indexв little-endianu64
3.3. Manager allowance PDA
- seed prefix:
shine_p_manager_allow - второй seed:
manager_wallet.as_ref()
4. Состояния программы
4.1. ConfigState
Поля:
version: u8dao_wallet: Pubkeyinflow_vault: Pubkey
4.2. CoefLimitState
Поля:
version: u8coef_ppm: u64limit_usd_cents: u64call_reward_lamports: u64
Смысл:
coef_ppm— множитель payout в ppm;limit_usd_cents— лимит суммы очереди Q1;call_reward_lamports— награда вызывающемуstep_payout.
4.3. QueuesState
Поля агрегатов:
q1_tickets_totalq1_tickets_paidq1_sum_total_usd_centsq1_sum_paid_usd_centsq2_tickets_totalq2_tickets_paidq2_sum_total_usd_centsq2_sum_paid_usd_centsq3_tickets_totalq3_tickets_paidq3_sum_total_usd_centsq3_sum_paid_usd_cents
4.4. TicketState
Поля:
version: u8queue_id: u8index: u64is_paid: boolrecipient_wallet: Pubkeypayout_usd_cents: u64debt_before_usd_cents: u64
4.5. ManagerAllowanceState
Поля:
version: u8manager_wallet: Pubkeyq1_available_usd_cents: u64q2_available_usd_cents: u64q3_available_usd_cents: u64
4.6. VaultState
Поля:
version: u8
Это техническая метка существования inflow PDA. Лампорты лежат на самом PDA-аккаунте.
5. Константы и экономика
Текущие базовые значения:
COEF_SCALE_PPM = 1_000_000START_COEF_PPM = 5_000_000(5.0x)START_LIMIT_USD_CENTS = 1_000_000(10_000 USD)START_CALL_REWARD_LAMPORTS = 8_000_000MAX_CALL_REWARD_LAMPORTS = 10_000_000ORACLE_MAX_AGE_SECS = 120- цена SOL/USD берётся из Pyth.
6. Инструкция init
Назначение
Инициализировать все базовые PDA программы.
Создаваемые PDA
config_pdacoef_limit_pdaqueues_pdainflow_vault_pda
Кто может вызвать
Технически любой signer, если система ещё не инициализирована.
Что записывается
dao_walletберётся из settings/deploy config;inflow_vaultуказывается как PDA самой программы;- коэффициенты и лимиты пишутся стартовыми значениями;
- очереди стартуют с нулями.
7. Инструкция update_coef_limit
Назначение
Изменить коэффициент выплат, лимит очереди Q1 и награду за шаг выплат.
Авторизация
Только dao_wallet из ConfigState.
Проверки
- signer =
config.dao_wallet coef_ppm > 0limit_usd_cents > 0call_reward_lamports <= MAX_CALL_REWARD_LAMPORTS
8. Инструкция grant_manager_limits
Назначение
Выдать менеджеру квоты на добавление тикетов вручную.
Авторизация
Только DAO.
Поведение
- создаёт
manager_allowance_pda, если её ещё нет; - увеличивает доступные лимиты Q1/Q2/Q3 для указанного менеджера;
- не добавляет тикеты сама.
9. Инструкции покупки тикета
Есть три входных варианта:
buy_ticketbuy_ticket_usdbuy_ticket_sol
Все они в итоге сводятся к одному действию:
- рассчитывается
purchase_usd_cents; - создаётся ticket в очереди
Q1; - обновляются агрегаты очереди
Q1.
9.1. Общие правила
- ticket создаётся только в
Q1; - очередь
Q1временно блокируется, если достигнут её суммарный лимит; payout_usd_cents = purchase_usd_cents * coef_ppm / COEF_SCALE_PPM;recipient_walletзаписывается в ticket.
9.2. Важная деталь денежного потока
В текущей реализации при покупке тикета лампорты переводятся:
- не во inflow vault;
- а напрямую в
dao_wallet.
То есть:
- ticket фиксирует долг на будущую выплату;
- а inflow vault — отдельный источник средств для исполнения payouts.
Эта деталь обязательно должна быть осознана при переписи: либо её сохраняют как сознательную модель, либо отдельно меняют и тогда обновляют этот документ.
10. Инструкция manager_add_ticket
Назначение
Дать менеджеру возможность вручную создать ticket в Q1, Q2 или Q3.
Авторизация
Signer должен совпадать с manager_wallet в manager_allowance_pda.
Проверки
queue_idтолько1,2или3;payout_usd_cents > 0;- доступный allowance по очереди не меньше суммы тикета;
- ticket PDA ещё не существует.
Эффект
- создаётся ticket;
- allowance уменьшается;
- агрегаты соответствующей очереди увеличиваются.
11. Инструкция step_payout
Назначение
Сделать один шаг исполнения очереди выплат.
Выбор очереди
- если в
Q1есть pending ticket, сначала обслуживаетсяQ1; - если
Q1пустая, обслуживаетсяQ2; - если
Q1иQ2пустые, обслуживаетсяQ3.
Что считается pending
pending = tickets_total - tickets_paid
Как выбирается ticket
Берётся следующий индекс:
next_index = tickets_paid + 1
Проверки
config_pda,coef_limit_pda,queues_pda,inflow_vault_pdaвалидны;dao_walletсовпадает сconfig.dao_wallet;next_ticket_pdaсовпадает с PDA следующего тикета;- тикет ещё не оплачен;
ticket_recipient_walletсовпадает сticket.recipient_wallet;- в inflow vault достаточно средств на весь шаг.
Сколько переводится
Для target queue:
- получателю тикета переводится
ticket_lamports; - DAO переводится
dao_lamports; - вызвавшему шаг переводится
call_reward_lamports.
dao_multiplier:
- для
Q1=1 - для
Q2=2 - для
Q3=3
То есть DAO получает:
1x payout_usdдля Q1;2x payout_usdдля Q2;3x payout_usdдля Q3.
Источник денег
Все три перевода идут из inflow vault PDA.
Если pending нет
Если все три очереди пусты:
- весь доступный остаток inflow vault переводится в DAO wallet.
12. Инструкция change_ticket_recipient
Назначение
Позволить текущему получателю тикета поменять адрес получения, пока ticket ещё не исполнен.
Авторизация
Только текущий ticket.recipient_wallet.
Ограничения
Нельзя менять получателя у следующего тикета на выплату в активной очереди.
Логика:
- если в
Q1есть pending — следующий ticket определяется вQ1; - иначе если в
Q2есть pending — следующий ticket берётся вQ2; - иначе следующий ticket берётся в
Q3; - если текущий ticket и есть этот ближайший ticket, смена recipient запрещена.
13. Pyth oracle и конвертация
Программа использует Pyth SOL/USD.
Проверки oracle:
- передан именно тот oracle account, что указан в settings;
- feed id совпадает с ожидаемым;
- цена не старше
ORACLE_MAX_AGE_SECS; - цена положительная и корректно переводима в ratio.
Внутренние преобразования:
lamports -> usd_centsделаются с округлением вниз;usd_cents -> lamportsделаются с округлением вверх.
Это важно, чтобы не недоплачивать обязательства при payout.
14. Ошибки и классы отказа
Программа должна различать как минимум:
- неверный inflow vault;
- неверный DAO wallet;
- неавторизованный DAO;
- неавторизованный manager;
- неверный manager allowance PDA;
- неверный queue id;
- тикет уже выплачен;
- неверный recipient;
- нельзя сменить recipient ближайшего тикета;
- недостаточно средств inflow vault для step payout;
- queue temporarily paused из-за лимита;
- oracle account/feed/price invalid;
- slippage exceeded.
15. Что должно сохраниться при переписи без Anchor
Обязательно сохранить:
- те же PDA seed-правила;
- те же состояния и поля;
- ту же модель очередей Q1/Q2/Q3;
- ту же приоритетность
Q1 -> Q2 -> Q3вstep_payout; - ту же логику allowance менеджера;
- те же oracle-ограничения и округления;
- ту же текущую модель денежных потоков, если она не меняется отдельным решением.
Если при переписи вы решите поменять экономическую модель, сначала нужно обновить этот документ.