Compound’s governance system is powered by COMP token, which is distributed to users of the protocol. COMP token holders receive voting power on a 1–1 basis to the amount of COMP held; this voting power can be delegated to any address, and then can be used to vote on proposals.
There are two methods by which a user can delegate their voting rights or cast votes on proposals: either calling the relevant functions (delegate, castVote) directly; or using by-signature functionality (delegateBySig, castVotebySig).
A key benefit to users of by-signature functionality is that they can create a signed delegate or vote transaction for free, and have a trusted third-party spend ETH on gas fees and write it to the blockchain for them. In this guide, we will focus on code examples around this type of functionality.
Delegate By Signature
By using an EIP-712 “typed-structured data” signature, COMP token holders can delegate their voting rights to any Ethereum address. The COMP smart contract’s delegateBySig method is available to users that have a signed delegation transaction.
A use case for these signatures might be that a delegate wishes to recruit COMP holders to delegate their votes to the delegatee, and to enable them to do so with very low friction.
The delegatee can create a web page where users sign a delegateBySig transaction using MetaMask and their private key, which would then be posted to the delegatee’s web server. Later on, the delegatee can batch signatures into a single Ethereum transaction, and officially collect the voting rights of their constituents by executing the delegateBySig method.
Cast Vote By Signature
With the same type of signature as delegateBySig, users can enable a third party to submit a vote on their behalf in any single Compound governance proposal. The Governor smart contract’s castVoteBySig method is available to anyone that has a signed vote transaction.
The third party in this scenario, submitting the COMP-holder’s signed transaction, could be the same as in the delegateBySig example, however the voting power that they hold is for only a single proposal, instead of indefinitely. The signatory still holds the power to vote on their own behalf in the proposal if the third party has not yet published the signed transaction that was given to them.
Delegate By Signature in a Web3 Site
Using this code example, anyone can create a simple web page that enables users to delegate their voting rights, by signature, to another address. We’ll assume that all users that visit this page are using MetaMask to utilize Web3 functionality.
When a user visits the page, they can see their selected Web3 wallet address, and their current Compound governance delegate address. They can fill in the address of the third party that they want to delegate their voting rights to. In practice, this address can be hard-coded into the web page.
Next, the user will click “Create Delegation Signature” which will trigger a MetaMask approval of the data that is to be signed. The MetaMask documentation has an in-depth description of signing data.
Here is the event handler that executes when the user clicks the button.
The code utilizes the eth_signTypedData_v4 method, which is implemented within MetaMask. This is used to create typed-structured data signatures, which are described in the EIP-712 specification. In order to create a valid signature, the method needs 3 parameters.
- The delegatee’s Ethereum address.
- The nonce of the signatory account from the COMP smart contract.
- The transaction’s expiry time, in seconds since the Unix epoch.
The typed-structured data signature method accepts the signatory address alongside a JSON string. The EIP-712 specification defines the types, struct, and domain that make up the data that is to be signed. This is implemented in a simple method, which is called in the button-click event handler.
The delegatee, nonce, expiry, and signature can then be used by any Ethereum address to publish the delegate transaction. These are the parameters of the delegateBySig method in the COMP contract. The signature needs to be broken up into 3 parameters, known as v, r, and s.
Cast Vote By Signature in a Web3 Site
Just like in the Delegate By Signature example, a web page can be made for users to create a vote signature. If a user wishes to give away their “ballot” to a third party, that user must sign separate “for” and “against” transactions, and the third-party may choose which transaction to publish.
The structured data object for creating vote signatures is slightly different from a delegate signature. In this case, the user needs a “Ballot” definition.
Clicking “Create Vote Signatures” will prompt the user to sign a “for” and an “against” transaction. Similar to the delegateBySig method, votes can be cast by passing 2 parameters to the castVoteBySig method.
- The unique ID of the Compound governance proposal (auto-incrementing integer).
- A boolean value of the user’s support of the proposal (true or false).
Additionally, the user must pass the signature, which must be broken up into 3 parameters, known as v, r, and s (implemented in the previous section).
Batch and Publish Signatures with a Script or a Smart Contract
Once a user has collected signed transactions for Compound governance, they need to publish those transactions to the Ethereum blockchain. The code for implementing this could be useful for exchanges and wallet apps that manage many private keys for many different users.
A collection of signed transactions can be published to the blockchain all at once using JSON RPC or a smart contract. This example uses only Web3.js in a Node.js script. The script will create and batch-publish delegation signatures for a collection of private keys.
Gas used: 454740
The web3.BatchRequest object batches the delegateBySig transactions and publishes them to the blockchain. The full Node.js example is available in the governance examples GitHub repository. For more information on the Web3.js batching, see the documentation.
The same functionality can be implemented with a Solidity smart contract. By comparison, this method uses marginally less gas than the Web3 batch approach.
Gas used: 306046