Timelock Controller for Governance Development

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Timelock Controller for Governance Development
Medium
~2-3 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1214
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Developing DAO delegate system

Delegation solves on-chain governance's voter apathy problem. Most DAOs see real turnout of 5–15% circulating supply. Reasons: technical barriers (keep tokens unlocked + pay gas), information load (can't analyze every proposal), lack of motivation for small holders.

Delegation system lets token holders delegate voting power to specialized delegates — active community members who regularly vote and publicly justify positions. This is closer to representative democracy than direct voting.

Basic mechanism: ERC-20Votes delegation

Standard ERC-20Votes (OpenZeppelin) provides built-in delegation via checkpoint system.

// ERC-20Votes adds:
// - delegate(address delegatee) — delegate voting power
// - delegates(address account) → address — current delegate
// - getVotes(address account) → uint256 — current voting power
// - getPastVotes(address account, uint256 blockNumber) → uint256
// - getPastTotalSupply(uint256 blockNumber) → uint256

// Example: holder delegates all voting power
token.delegate(delegateAddress);

// Self-delegation (needed to get own voting power)
token.delegate(msg.sender);

Critical moment: after buying tokens, user has zero voting power until calling delegate. This unintuitive behavior must be explicitly shown in UI. Many large Uniswap holders don't participate in governance simply because they never delegated.

Checkpoint mechanism: delegate records voting power snapshot at call time. Governor uses getPastVotes(voter, proposalSnapshot) — voting power at proposal creation block. Buying tokens after snapshot doesn't give power in current vote.

Partial delegation

Basic ERC-20Votes supports delegating only wholly to one address. For advanced systems you need partial: 40% to delegate A, 30% to delegate B, 30% keep self.

ERC-20VotesPartialDelegation (EIP-5805 extension)

struct Delegation {
    address delegatee;
    uint96 numerator; // fraction in numerator/denominator
}

// User delegates fractionally
function delegateMulti(Delegation[] calldata delegations) external {
    uint256 totalNumerator;
    for (uint i = 0; i < delegations.length; i++) {
        totalNumerator += delegations[i].numerator;
    }
    require(totalNumerator <= DENOMINATOR, "Exceeds 100%");
    
    _updateDelegations(msg.sender, delegations);
}

This significantly complicates checkpoint logic: each transfer must recalculate contributions to each delegate. Gas costs grow with delegate count.

Ready implementations: Compound Governor Bravo supports basic delegation; for fractional — look at OpenZeppelin GovernorCountingFractional + custom token.

Delegate profiles and reputation

Technical delegation mechanism is one thing. Infrastructure for deciding whom to delegate to — another.

On-chain delegate registry

contract DelegateRegistry {
    struct DelegateProfile {
        string name;
        string ipfsMetadata; // CIDv1 hash of IPFS doc with full profile
        string[] interests;  // tags: ["defi", "security", "tokenomics"]
        uint256 createdAt;
    }
    
    mapping(address => DelegateProfile) public profiles;
    
    event ProfileUpdated(address indexed delegate, string ipfsMetadata);
    
    function setProfile(
        string calldata name,
        string calldata ipfsMetadata,
        string[] calldata interests
    ) external {
        profiles[msg.sender] = DelegateProfile({
            name: name,
            ipfsMetadata: ipfsMetadata,
            interests: interests,
            createdAt: block.timestamp
        });
        emit ProfileUpdated(msg.sender, ipfsMetadata);
    }
}

IPFS metadata contains: platform statement (position on key questions), voting history with rationales, contact info. Storing full profile off-chain (IPFS) reduces gas, on-chain only hash for verification.

Delegate activity statistics

Real delegate value is track record. Subgraph should index:

  • Participation rate: % of proposals voted on during delegation period
  • Alignment: how often delegate position matches final result (not best metric, but informative)
  • Rationale rate: % of votes with published reasoning
// The Graph subgraph mapping
export function handleVoteCast(event: VoteCast): void {
    let delegate = Delegate.load(event.params.voter.toHex());
    if (!delegate) {
        delegate = new Delegate(event.params.voter.toHex());
        delegate.totalVotes = BigInt.zero();
        delegate.participationRate = BigDecimal.zero();
    }
    
    delegate.totalVotes = delegate.totalVotes.plus(BigInt.fromI32(1));
    delegate.save();
    
    let vote = new Vote(event.transaction.hash.toHex() + '-' + event.logIndex.toString());
    vote.delegate = delegate.id;
    vote.proposalId = event.params.proposalId;
    vote.support = event.params.support;
    vote.weight = event.params.weight;
    vote.reason = event.params.reason;
    vote.save();
}

Gasless delegation via EIP-712

Delegation requires on-chain transaction. At $2–5 gas cost this is barrier for small holders. Solution — delegateBySig: holder signs delegation off-chain, relayer sends transaction and pays gas.

// Built into ERC-20Votes (OpenZeppelin)
function delegateBySig(
    address delegatee,
    uint256 nonce,
    uint256 expiry,
    uint8 v,
    bytes32 r,
    bytes32 s
) public virtual {
    require(block.timestamp <= expiry, "Expired");
    address signer = ECDSA.recover(
        _hashTypedDataV4(keccak256(abi.encode(
            DELEGATION_TYPEHASH,
            delegatee,
            nonce,
            expiry
        ))),
        v, r, s
    );
    require(nonce == _useNonce(signer), "Invalid nonce");
    _delegate(signer, delegatee);
}

Frontend flow: user signs EIP-712 message (MetaMask shows readable text "Delegate [address] until [date]"), signature sent to backend, backend calls delegateBySig and pays gas.

Batch gasless delegation for onboarding

When onboarding new user can request delegate-signature for multiple tokens:

const delegations = await Promise.all([
    signDelegation(tokenA, delegatee, signer),
    signDelegation(tokenB, delegatee, signer),
]);

// Single relayer submit
await Promise.all(delegations.map(d => relayer.submit(d)));

Revoking delegation and re-delegation

Delegation is not permanent. delegate(address(0)) or delegate(msg.sender) revokes delegation. Changing delegate: delegate(newDelegatee) automatically transfers all voting power.

Effective time: change effective next block. If proposal already created and snapshot fixed — change doesn't affect current vote.

UI recommendations: show user current delegate, their participation rate last 30 days, deadline of nearest active votes. This reduces churn of delegators disappointed by inactive delegate.

Incentive mechanics for delegates

Pure altruistic model works only for large holders. For systemic work you need incentives.

Delegate rewards. Protocol pays delegates for active participation. Calculation: participation rate × delegated voting power × reward rate. Distributed from treasury through governance-approved contract.

Streaming payments (Sablier/SuperFluid). Delegate gets constant stream while participation rate above threshold. Below threshold — stream auto-pauses via keeper.

// Keeper checks participation rate
function checkAndUpdateDelegateStream(address delegate) external {
    uint256 participationRate = stats.getParticipationRate(delegate, 30 days);
    if (participationRate < MIN_PARTICIPATION_RATE) {
        sablier.cancel(delegateStreams[delegate]);
        emit DelegateStreamPaused(delegate, participationRate);
    }
}

Stack and timeline

Contracts: ERC-20Votes extension + DelegateRegistry + reward distributor. Solidity + Foundry + OpenZeppelin 5.x. Indexing: The Graph subgraph for delegate stats. Frontend: React + wagmi + react-query.

Component Timeline
ERC-20Votes + gasless delegation 1 week
DelegateRegistry on-chain 3–5 days
The Graph subgraph for stats 1 week
Frontend: delegate search, delegation UI 2 weeks
Reward mechanics 1–2 weeks

MVP (basic delegation + gasless + delegate profiles UI): 4–5 weeks. Full system with reward streaming and analytics: 2 months.