Разработка системы управления протоколом на нескольких сетях

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка системы управления протоколом на нескольких сетях
Сложная
~1-2 недели
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1258
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1170
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    873
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1092
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    563
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    830

Разработка агрегатора голосований нескольких DAO

Крупный держатель токенов, участвующий в нескольких DAO одновременно, сталкивается с операционным хаосом: Uniswap Governor, Compound Governor, Aave AIP, ENS Governor — у каждого свой интерфейс, своя логика пропозалов, свои сроки. Пропустить deadline голосования — обычное дело. Агрегатор governance решает эту проблему: единый дашборд, единая точка мониторинга, возможность делегировать управление правилам.

Это не просто read-only дашборд (таким является Tally). Агрегатор с on-chain компонентом позволяет одной транзакцией голосовать в нескольких DAO, управлять делегированием в одном месте, и задавать автоматические правила голосования.

Архитектура системы

Уровни агрегации

Агрегатор состоит из трёх слоёв:

Indexing layer — индексирует события из всех отслеживаемых Governor-контрактов. Каждый протокол имеет свою специфику событий (Governor Bravo vs OZ Governor различаются), поэтому нужны адаптеры.

State layer — единая модель данных для пропозалов из разных источников. Нормализация: несмотря на разные контракты, все пропозалы имеют общую структуру {id, protocol, status, deadline, description, calldata}.

Action layer — on-chain или off-chain механизм исполнения голосований. Это самый сложный компонент.

Адаптеры протоколов

Каждый Governor-совместимый контракт имеет немного разный ABI. OZ Governor v4/v5 отличается от Compound Governor Bravo, который отличается от Compound Governor Alpha.

interface GovernorAdapter {
    getProposals(fromBlock: number): Promise<Proposal[]>;
    getProposalState(proposalId: bigint): Promise<ProposalState>;
    castVote(proposalId: bigint, support: number): Promise<TransactionRequest>;
    getVotingPower(voter: string, blockNumber: number): Promise<bigint>;
}

class OZGovernorAdapter implements GovernorAdapter {
    constructor(private contract: Contract) {}
    
    async getProposals(fromBlock: number) {
        const filter = this.contract.filters.ProposalCreated();
        const events = await this.contract.queryFilter(filter, fromBlock);
        return events.map(e => this.normalizeProposal(e));
    }
    
    async castVote(proposalId: bigint, support: number) {
        return {
            to: this.contract.target,
            data: this.contract.interface.encodeFunctionData('castVote', [proposalId, support])
        };
    }
}

class CompoundBravoAdapter implements GovernorAdapter {
    // Compound Bravo использует uint вместо enum для support
    // и имеет другую структуру ProposalCreated события
    async castVote(proposalId: bigint, support: number) {
        return {
            to: this.contract.target,
            data: this.contract.interface.encodeFunctionData('castVote', [proposalId, support])
        };
    }
}

На момент разработки стоит проверить наличие готовых адаптеров в библиотеках типа wagmi/viem action libraries или The Graph subgraphs от каждого протокола.

On-chain компонент: мультивызов голосований

Контракт-агрегатор позволяет голосовать в нескольких DAO одной транзакцией. Основан на паттерне Multicall.

contract GovernanceAggregator {
    struct VoteInstruction {
        address governor;   // адрес Governor контракта
        uint256 proposalId;
        uint8 support;      // 0=Against, 1=For, 2=Abstain
        bytes reason;       // опциональное обоснование
    }
    
    mapping(address => mapping(address => bool)) public authorizedDelegates;
    
    modifier onlyAuthorized(address voter) {
        require(
            msg.sender == voter || authorizedDelegates[voter][msg.sender],
            "Not authorized"
        );
        _;
    }
    
    function batchVote(
        address voter,
        VoteInstruction[] calldata instructions
    ) external onlyAuthorized(voter) {
        for (uint i = 0; i < instructions.length; i++) {
            VoteInstruction calldata inst = instructions[i];
            
            // Используем try/catch: один failed vote не блокирует остальные
            try IGovernor(inst.governor).castVoteWithReason(
                inst.proposalId,
                inst.support,
                string(inst.reason)
            ) {
                emit VoteCast(voter, inst.governor, inst.proposalId, inst.support);
            } catch Error(string memory reason) {
                emit VoteFailed(voter, inst.governor, inst.proposalId, reason);
            }
        }
    }
}

Важный нюанс: каждый Governor контракт проверяет msg.sender как voter. Агрегатор голосует от своего имени — это работает только если voting power делегирована на адрес агрегатора. Альтернативный подход — агрегатор как smart wallet, который вызывает Governor через DELEGATECALL — но это значительно усложняет безопасность.

Делегирование на агрегатор

Для ERC-20Votes совместимых токенов пользователь делегирует voting power на адрес агрегатора:

// Пользователь один раз делегирует каждый токен
await uniToken.delegate(aggregatorAddress);
await compToken.delegate(aggregatorAddress);

// Агрегатор теперь может голосовать voting power пользователя
// НО: агрегатор голосует за всех делегировавших вместе
// Нужна логика разделения voice если мнения расходятся

Это фундаментальная проблема: агрегатор, получив делегирование от 1000 пользователей, должен голосовать единым голосом. Если 60% хотят For, а 40% — Against — как голосовать?

Решение — proportional voting: агрегатор голосует своим весом пропорционально предпочтениям пользователей. Compound Governor Bravo поддерживает castVoteWithWeightBySig для этого. OZ Governor v5 добавил fractionalVoting через GovernorCountingFractional модуль.

Правила автоматического голосования

Ключевая фича продвинутого агрегатора — автоматическое исполнение голосований по заданным правилам.

interface VotingRule {
    protocol: string;          // "uniswap" | "compound" | "*"
    proposalType: string;      // "parameter-change" | "treasury" | "*"
    conditions: Condition[];   // логические условия
    defaultVote: 0 | 1 | 2;   // Against/For/Abstain если условия не сработали
    requireConfirmation: boolean; // запросить подтверждение у voter
}

// Пример правила: всегда голосовать Against treasury proposals > $1M
const rule: VotingRule = {
    protocol: "*",
    proposalType: "treasury",
    conditions: [{
        field: "requestedAmount",
        operator: ">",
        value: 1_000_000_000000 // $1M в USDC (6 decimals)
    }],
    defaultVote: 0, // Against
    requireConfirmation: false
};

Применение правил — off-chain процесс. Сервис анализирует новые пропозалы, запускает proposal parser (извлекает тип и параметры из calldata), применяет правила пользователя и генерирует batch vote инструкцию.

Parsing calldata пропозалов

Из calldata пропозала нужно извлечь смысл. Например, пропозал Compound на изменение collateral factor:

const KNOWN_SIGNATURES = {
    '0x3c3e4f7a': { name: 'setCollateralFactor', protocol: 'compound' },
    '0x5b85a600': { name: '_setReserveFactor', protocol: 'compound' },
    // ... другие известные селекторы
};

function parseProposalCalldata(calldata: string): ProposalAction {
    const selector = calldata.slice(0, 10);
    const known = KNOWN_SIGNATURES[selector];
    if (!known) return { type: 'unknown', selector };
    
    const iface = new ethers.Interface([`function ${known.name}(...)`]);
    const decoded = iface.decodeFunctionData(known.name, calldata);
    return { type: known.name, protocol: known.protocol, params: decoded };
}

Это трудоёмкая но необходимая работа для каждого поддерживаемого протокола.

Уведомления и дедлайны

Агрегатор без уведомлений о дедлайнах теряет половину ценности. Система уведомлений:

Мониторинг: polling активных пропозалов каждые N минут или подписка на события через WebSocket (eth_subscribe logs).

Уведомления: email / Telegram / webhook при: создании нового пропозала в отслеживаемом DAO, приближении дедлайна (например, за 24 часа), исполнении/отклонении пропозала.

Дедлайн расчёт: Governor хранит proposalDeadline как номер блока. Конвертация в timestamp: deadlineBlock * avgBlockTime + referenceTimestamp. Точность ±5 минут — достаточно для уведомлений.

Стек разработки

Backend: Node.js + TypeScript + viem + BullMQ (очереди задач для парсинга и уведомлений) + PostgreSQL (хранение состояния пропозалов и правил).

Indexing: The Graph subgraphs для основных протоколов (Uniswap, Compound, Aave субграфы существуют) + собственный listener для остальных.

Frontend: React + wagmi + Radix UI. Ключевые экраны: дашборд активных пропозалов, управление правилами, история голосований.

Smart contract: Solidity 0.8.x + Foundry для тестирования. Контракт агрегатора — минимальный, основная логика off-chain.

Функция Срок
Базовый indexer (3–5 протоколов) 2–3 недели
On-chain batch vote контракт 1 неделя
Правила и автоматизация 2–3 недели
Frontend дашборд 2–3 недели
Уведомления 1 неделя

MVP с ручным batch voting и дашборд без автоматики — 4–5 недель. Полный агрегатор с правилами — 2–3 месяца.

Стоимость зависит от количества поддерживаемых протоколов и глубины автоматизации.