Documentation
Tutorials
Create a Voting Client

Creating a Basic Client

In this tutorial we build a Flexible Voting Client, a contract that can be delegated votes and then is able to cast them to a compatible Governor. These contracts help support some of the situations we find token holders trading off their voting power for some other benefit. This guide will demonstrate how to build clients quickly for custom use cases.

Below we will build a simple client where token holders deposit their tokens into a Pool contract that earns yield while at the same time not wanting to lose their voting power.

Step 1: Setup pool contract

When creating our pool contract we will focus on the key pieces needed to support Flexible Voting. Below, you can find our example pool contract which handles users depositing tokens into the Pool. We decided to omit implementing the withdraw and yield functionality as they are not relevant to Flexible Voting.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
 
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ERC20Votes} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
 
contract SimplePool {
  using SafeERC20 for ERC20Votes;
 
  /// @notice Token used for deposits into the pool.
  ERC20Votes public immutable token;
  mapping(address => uint128) internal _deposits;
 
  constructor(address _token) {
    token = ERC20Votes(_token);
  }
 
  function depositsOf(address _depositor) public view returns (uint128) {
    return _deposits[_depositor];
  }
 
  /// @param _amount Amount of tokens to deposit.
  function deposit(uint128 _amount) public virtual {
    // transfer amount
    _deposits[msg.sender] += _amount;
    token.safeTransferFrom(msg.sender, address(this), _amount);
  }
 
  /// @notice For the purposes of this tutorial we will not implement the withdraw function as it is
  /// not relevant to Flexible voting
  function withdraw() public virtual {}
}

Step 2: Inherit FlexVotingClient

FlexVotingClient is contract that provides all of the functionality needed to create a Flexible Voting Client. The only method a client creator must override is _rawBalanceOf. This method determines the number of tokens a _user of the client has claim to in the system which also determines the _users voting power.

contract PoolClient is SimplePool, FlexVotingClient {
  /// @param _token The address of the token deposited into the pool.
  /// @param _governor The address of the flex-voting-compatible governance contract.
  constructor(address _token, address _governor) SimplePool(_token) FlexVotingClient(_governor) {}
 
  function _rawBalanceOf(address _user) internal view virtual override returns (uint256) {
    return _deposits[_user];
  }
}

Conclusion

We have created a minimal Flexible Voting example. Users can now vote after depositing their Governance tokens into a yield earning pool.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
 
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ERC20Votes} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
 
contract SimplePool {
  using SafeERC20 for ERC20Votes;
 
  /// @notice Token used for deposits into the pool.
  ERC20Votes public immutable token;
  mapping(address => uint128) internal _deposits;
 
  constructor(address _token) {
    token = ERC20Votes(_token);
  }
 
  function depositsOf(address _depositor) public view returns (uint128) {
    return _deposits[_depositor];
  }
 
  /// @param _amount Amount of tokens to deposit.
  function deposit(uint128 _amount) public virtual {
    // transfer amount
    _deposits[msg.sender] += _amount;
    token.safeTransferFrom(msg.sender, address(this), _amount);
  }
 
  /// @notice For the purposes of this tutorial we will not implement the withdraw function as it is
  /// not relevant to Flexible voting
  function withdraw() public virtual {}
}
 
contract PoolClient is SimplePool, FlexVotingClient {
  /// @param _token The address of the token deposited into the pool.
  /// @param _governor The address of the flex-voting-compatible governance contract.
  constructor(address _token, address _governor) SimplePool(_token) FlexVotingClient(_governor) {}
 
  function _rawBalanceOf(address _user) internal view virtual override returns (uint256) {
    return _deposits[_user];
  }
}