בעל אסימונים גדול המשתתף ב-Uniswap, Compound, Aave ו-DAO אחרים נתקל מדי יום בממשקי Governor שונים, בלוגיקת הצעות ומועדים. החמצת מועד היא דבר נפוץ. האגרגטור שלנו מספק לוח מחוונים מאוחד, ניטור, האצלה וביצוע אוטומטי של הצבעות. לצוות שלנו יש ניסיון של 5+ שנים והוא סיפק למעלה מ-20 חוזים חכמים. צרו קשר לקבלת ייעוץ ארכיטקטוני המותאם לסט הטכנולוגי שלכם.
מערכת ארכיטקטורה
האגרגטור מורכב משלוש שכבות: שכבת אינדוקס — מאנדקסת אירועים מחוזי Governor באמצעות מתאמים (לכל פרוטוקול יש את הפרטים שלו — Governor Bravo שונה מ-OZ Governor). שכבת מצב — מודל נתונים אחיד: הצעות מנורמלות ל-{id, protocol, status, deadline, description, calldata}. שכבת פעולה — מנגנון 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 {
async castVote(proposalId: bigint, support: number) {
return {
to: this.contract.target,
data: this.contract.interface.encodeFunctionData('castVote', [proposalId, support])
};
}
}במהלך הפיתוח, בדקו מתאמים מוכנים בספריות כמו wagmi/viem או The Graph subgraphs.
רכיב on-chain: הצבעת אצווה
חוזה האגרגטור מאפשר הצבעה במספר DAOs בעסקה אחת באמצעות תבנית Multicall. קוד החוזה מינימלי וניתן לביקורת.
contract GovernanceAggregator {
struct VoteInstruction {
address governor;
uint256 proposalId;
uint8 support;
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 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 בודק את 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 { async castVote(proposalId: bigint, support: number) { return { to: this.contract.target, data: this.contract.interface.encodeFunctionData('castVote', [proposalId, support]) }; } } כמצביע. האגרגטור מצביע מהכתובת שלו — זה עובד רק אם כוח ההצבעה מואצל לכתובת האגרגטור. גישה חלופית היא שימוש בארנק חכם עם DELEGATECALL, אך זה מוסיף מורכבות אבטחה.
האצלה והצבעה פרופורציונלית
עבור אסימוני ERC-20Votes, המשתמש מאציל כוח הצבעה לכתובת האגרגטור. הבעיה: אם 60% רוצים בעד ו-40% נגד, האגרגטור צריך להצביע פרופורציונלית. הפתרון הוא הצבעה חלקית: האגרגטור מצביע עם המשקל שלו באופן פרופורציונלי. Compound Governor Bravo תומך ב-contract GovernanceAggregator { struct VoteInstruction { address governor; uint256 proposalId; uint8 support; 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 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); } } } } , ו-OZ Governor v5 הוסיף msg.sender.
כללי הצבעה אוטומטיים
תכונה מרכזית היא ביצוע אוטומטי של הצבעות על בסיס כללים מוגדרים מראש. דוגמה: תמיד להצביע נגד הצעות אוצר > $1M. שירות off-chain מנתח הצעות חדשות, מחלץ פרמטרים מ-calldata ומיישם כללים. אם התנאים לא מתקיימים, נעשה שימוש בהצבעת ברירת מחדל.
interface VotingRule {
protocol: string;
proposalType: string;
conditions: Condition[];
defaultVote: 0 | 1 | 2;
requireConfirmation: boolean;
} ניתוח calldata של הצעות
אנחנו צריכים לחלץ משמעות מ-calldata. לדוגמה, הצעת Compound לשינוי גורם בטחונות:
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 };
}זו עבודה אינטנסיבית עבור כל פרוטוקול.
התראות ומועדים
מערכת התראות: סקירת הצעות פעילות כל N דקות או הרשמה דרך WebSocket (castVoteWithWeightBySig). התראות דרך אימייל / טלגרם / webhook על יצירת הצעה, מועד מתקרב (24 שעות לפני) וביצוע. חישוב מועד: GovernorCountingFractional, דיוק ±5 דקות.
סט פיתוח
Backend: Node.js + TypeScript + viem + BullMQ + PostgreSQL. אינדוקס: The Graph subgraphs + מאזין מותאם. Frontend: React + wagmi + Radix UI. חוזה חכם: Solidity 0.8.x + Foundry.
מה כלול בעבודה
| רכיב | תיאור |
|---|---|
| ניתוח פרוטוקול | זיהוי DAOs נתמכים וחוזי Governor שלהם |
| פיתוח מתאמים | יצירת מנרמלים עבור כל פרוטוקול |
| חוזה חכם אגרגטור | יישום הצבעת אצווה עם try/catch |
| Backend לאינדוקס וכללים | ניטור, ניתוח, הצבעה אוטומטית |
| לוח מחוונים Frontend | צפייה בהצעות וניהול כללים |
| התראות | התראות טלגרם/אימייל |
| ביקורת אבטחה | בדיקת חוזה ו-Backend |
| תיעוד | API, כללים, ארכיטקטורה |
השוואה להצבעה ידנית
האגרגטור מפחית את זמן הניטור פי 3. במקום 5–10 דקות לבדיקת כל ההצעות הפעילות, זה לוקח דקה אחת בלוח המחוונים. כללים אוטומטיים מבטלים טעויות אנוש. צרו קשר לקבלת הערכת פרויקט על בסיס מספר הפרוטוקולים.
מדוע הגישה שלנו אמינה יותר?
כל חוזה נבדק עבור reentrancy, overflow, ומשתמש באימות פורמלי עם Echidna (fuzzing). לצוות שלנו יש 5+ שנים בפיתוח blockchain. האגרגטור אינו מאחסן כספי משתמשים; כל העסקאות נחתמות על ידי נציג מורשה.
לוחות זמנים משוערים
| תכונה | לוח זמנים |
|---|---|
| אינדוקס בסיסי (3–5 פרוטוקולים) | 2–3 שבועות |
| חוזה הצבעת אצווה on-chain | שבוע אחד |
| כללים ואוטומציה | 2–3 שבועות |
| לוח מחוונים Frontend | 2–3 שבועות |
| התראות | שבוע אחד |
MVP עם הצבעת אצווה ידנית לוקח 4–5 שבועות. אגרגטור מלא לוקח 2–3 חודשים. צרו קשר — נחשב עלות ולוחות זמנים עבור הפרוטוקולים הספציפיים שלכם.







