Стандартні грантові механізми спотворюють реальну підтримку спільноти: великі донори отримують непропорційно великий вплив, а безліч дрібних учасників залишаються непоміченими. Квадратичне фінансування (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.







