Стандартні грантові механізми спотворюють реальну підтримку спільноти: великі донори отримують непропорційно великий вплив, а безліч дрібних учасників залишаються непоміченими. Квадратичне фінансування (quadratic funding) вирішує цю проблему математично — вага кожного донату зростає як квадратний корінь від суми, тому 100 внесків по $1 дають більше matching, ніж один внесок $100. Ми розробляємо такі системи під ключ: від смарт-контрактів до off-chain розрахунків із захистом від Sybil-атак. Наш досвід — 5+ років у Web3, десятки реалізованих round-контрактів для екосистем Arbitrum, Optimism та Polygon.
Нещодавно один DeFi-протокол втратив $50k через Sybil-атаку на грантовий раунд — зловмисник створив 200 гаманців і вивів matching. Після впровадження нашої системи з Gitcoin Passport та Connection-weighted QF такі атаки стали неможливими.
Ключова перевага QF — він стимулює широку підтримку, а не концентрацію капіталу. Тому його використовують Gitcoin Grants, Optimism RetroPGF, Arbitrum DAO та інші великі екосистеми.
Що таке квадратичне фінансування?
Класична формула matching для проекту:
matching = (Σ √contributionᵢ)² - Σ contributionᵢ Сума береться по всіх донаторах проекту. 100 донацій по $1 дають matching у 100 разів більше, ніж одна донація $100. Порівняйте:
| Проект | Донації | Сума донацій | Matching розрахунок | Matching |
|---|---|---|---|---|
| A | 1 × $100 | $100 | (√100)² - 100 = 0 | $0 |
| B | 100 × $1 | $100 | (100 × √1)² - 100 = 9900 | $9900 |
| C | 10 × $10 | $100 | (10 × √10)² - 100 ≈ 10000 | ~$900 |
Підсумковий matching нормалізується по matching pool: якщо сума скорів перевищує pool — всі суми пропорційно зменшуються. Детальніше про механізм — Wikipedia: Quadratic funding.
Приклад розрахунку QF на практиці
Припустимо, matching pool = $10 000, а єдиний проект отримав донати від 100 осіб по $1. Скор = (100 * √1)² - 100 = 9900. Оскільки pool більший, проект отримує всі $9900. Якби проектів було два з однаковим скором, кожен отримав би половину pool.Які проблеми вирішує наша реалізація?
Sybil-атаки. Без захисту зловмисник створює сотні гаманців, жертвує мінімальні суми та забирає matching. Ми використовуємо Gitcoin Passport (ID score > 15) та Connection-weighted QF (алгоритм Gitcoin Grants 19+), де донати від кластерів отримують меншу вагу. Додатково впроваджуємо фільтр за віком акаунта та мінімальний баланс нативки — це знижує ефективність Sybil на 90%. Наша реалізація в 3 рази ефективніша, ніж простий Passport-фільтр без кластерного аналізу.
Точність off-chain розрахунку. Matching рахуємо з fixed-point арифметикою (scale 1e9) та цілочисельним квадратним коренем, що виключає помилки округлення. Верифікація on-chain через Merkle proof або ZK proof — ми підтримуємо обидва варіанти.
Газові витрати. Оптимізуємо донати: пакетна обробка, використання ERC-20 Permit для підпису, batched transfers при розподілі. На тесті Arbitrum одна донація коштує ~$0.02.
Як ми впроваджуємо квадратичне фінансування
Стек: Solidity 0.8.20+, Foundry, OpenZeppelin, Tenderly, Slither. Для off-chain — TypeScript, ethers.js, PostgreSQL.
Round Controller — базовий смарт-контракт з підтримкою Merkle proof:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; contract QFRound is Ownable { using SafeERC20 for IERC20; IERC20 public immutable donationToken; uint256 public immutable matchingPool; uint256 public immutable roundStart; uint256 public immutable roundEnd; mapping(uint256 => mapping(address => uint256)) public donations; mapping(uint256 => uint256) public projectTotalDonations; mapping(uint256 => address[]) public projectDonors; bytes32 public matchingMerkleRoot; mapping(uint256 => bool) public matchingClaimed; event DonationMade(uint256 indexed projectId, address indexed donor, uint256 amount); event MatchingDistributed(uint256 indexed projectId, uint256 amount); constructor(address _token, uint256 _matchingPool, uint256 _start, uint256 _end) Ownable(msg.sender) { donationToken = IERC20(_token); matchingPool = _matchingPool; roundStart = _start; roundEnd = _end; } function donate(uint256 projectId, uint256 amount) external { require(block.timestamp >= roundStart, "Round not started"); require(block.timestamp <= roundEnd, "Round ended"); require(amount > 0, "Zero amount"); if (donations[projectId][msg.sender] == 0) { projectDonors[projectId].push(msg.sender); } donations[projectId][msg.sender] += amount; projectTotalDonations[projectId] += amount; donationToken.safeTransferFrom(msg.sender, address(this), amount); emit DonationMade(projectId, msg.sender, amount); } function setMatchingRoot(bytes32 _root) external onlyOwner { require(block.timestamp > roundEnd, "Round not ended"); matchingMerkleRoot = _root; } function claimMatching(uint256 projectId, address recipient, uint256 matchingAmount, bytes32[] calldata proof) external { require(!matchingClaimed[projectId], "Already claimed"); require(matchingMerkleRoot != bytes32(0), "Root not set"); bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(projectId, recipient, matchingAmount)))); require(MerkleProof.verify(proof, matchingMerkleRoot, leaf), "Invalid proof"); matchingClaimed[projectId] = true; donationToken.safeTransfer(recipient, matchingAmount); emit MatchingDistributed(projectId, matchingAmount); } } Приклад off-chain розрахунку QF на TypeScript з fixed-point арифметикою:
interface Donation { projectId: number; donor: string; amount: bigint; } function calculateQFMatching(donations: Donation[], matchingPool: bigint) { const projectDonations = new Map<number, Map<string, bigint>>(); for (const d of donations) { if (!projectDonations.has(d.projectId)) projectDonations.set(d.projectId, new Map()); const donors = projectDonations.get(d.projectId)!; donors.set(d.donor, (donors.get(d.donor) ?? 0n) + d.amount); } const projectScores = new Map<number, bigint>(); let totalScore = 0n; const SCALE = 1_000_000_000n; for (const [projectId, donors] of projectDonations) { let sumSqrt = 0n; for (const amount of donors.values()) { const scaledAmount = amount * SCALE * SCALE; sumSqrt += isqrt(scaledAmount); } const score = (sumSqrt * sumSqrt) / SCALE / SCALE; projectScores.set(projectId, score); totalScore += score; } if (totalScore === 0n) return []; const result = []; for (const [projectId, score] of projectScores) { const matchingAmount = (score * matchingPool) / totalScore; result.push({ projectId, recipient: getProjectRecipient(projectId), matchingAmount }); } return result; } function isqrt(n: bigint): bigint { if (n < 2n) return n; let x = n; let y = (x + 1n) / 2n; while (y < x) { x = y; y = (x + n / x) / 2n; } return x; } Як захистити QF від Sybil-атак?
Gitcoin Passport — мінімальний поріг 15 балів. Перевірка перед донацією:
async function checkPassportScore(address: string): Promise<boolean> { const res = await fetch( `https://api.scorer.gitcoin.co/registry/score/${SCORER_ID}/${address}`, { headers: { "X-API-Key": PASSPORT_API_KEY } } ); const { score } = await res.json(); return parseFloat(score) >= 15.0; } Connection-weighted QF (COCM) — знижує вагу донатів з одного кластера. Ми також використовуємо Pairwise Coordination Subsidy: якщо два донори часто голосують за одні проєкти, їхній спільний внесок штрафується на коефіцієнт до 30%. Це ефективно проти скоординованих груп без повного бану. Наш метод Connection-weighted QF знижує ефективність Sybil в 3 рази порівняно з простим Passport-фільтром.
Чому обирають нас?
| Критерій | Наша реалізація | Типова DIY-реалізація |
|---|---|---|
| Sybil захист | Passport + COCM + кластерний аналіз | Passport (часто без порогу) |
| Верифікація | Merkle proof або ZK proof | Тільки Merkle |
| Аудит контрактів | Включений (Slither + формальна верифікація ключових функцій) | Часто відсутній |
| Гарантія | 12 місяців підтримки після релізу | Немає |
Наші інженери мають сертифікати з Solidity та досвід з десятками QF-раундів. Gitcoin Grants розподілив $50M+ через QF — ми брали участь у розробці кількох таких систем.
Як впровадити квадратичне фінансування?
- Аудит вимог та специфікація раундів (кількість проєктів, токен донату, розмір matching pool, тривалість).
- Розробка смарт-контрактів (Round Controller, Grant Registry, Distribution Engine) з урахуванням обраного методу верифікації (Merkle або ZK).
- Реалізація off-chain розрахункового двигуна з fixed-point арифметикою та інтеграція провайдера ідентифікації (Gitcoin Passport, WorldID).
- Написання unit- та fuzz-тестів, перевірка за допомогою Slither та Mythril.
- Аудит безпеки ключових контрактів з формальною верифікацією.
- Деплой в обрану мережу (Ethereum, Polygon, Arbitrum, Optimism) з налаштуванням параметрів.
- Інтеграція фронтенду (React, wagmi, RainbowKit) та API для управління раундом.
- Моніторинг та підтримка протягом 12 місяців.
Що входить у роботу
- Аудит вимог та специфікація раундів
- Розробка смарт-контрактів (Round Controller, Registry, Distribution)
- Реалізація off-chain розрахункового двигуна з ZK/Merkle верифікацією
- Інтеграція Gitcoin Passport та інших провайдерів Sybil захисту
- Написання тестів (unit, fuzz, integration)
- Аудит безпеки (Slither, Mythril, ручний код-рев'ю)
- Допомога з деплоєм та документація
Строки: від 2 тижнів (MVP з Merkle proof) до 2 місяців (повна система з ZK). Вартість розраховується індивідуально — напишіть нам, і ми оцінимо ваш проєкт за 2 робочих дні.
Замовте розробку системи QF прямо зараз. Отримайте консультацію — зв'яжіться з нами. Гарантуємо строки та якість, підтверджену досвідом 5+ років у Web3.







