Wave Protocol is an onchain crowdfunding system built upon the Nouns Governance ecosystem to permissionlessly and meritocratically democratize access to Nouns contributions in the form of proposals.
At its heart, the protocol offers Nouns governance proposals as a service to the Nouns peripheral community who may not be capitalized enough to afford the 2 Nouns NFTs required to push their proposal onchain. The result is democratization of access to the Nouns sphere by lowering the barrier of entry for anyone with a worthy idea and desire to contribute.
The protocol makes use of currently unproductive Nouns token voting power to engender a competitive idea machine powered by the untapped market of non-tokenholder mindshare.
Economic incentives for each type of protocol participant (Nounders, Idea Creators, and Sponsors) are aligned by compensating Nouns token delegators with yield, granting Idea Creators competitive access to pushing Nouns proposals onchain, and Idea Sponsors with scouting provenance and lobbying opportunities.
Table of Contents
Protocol Overview
Wave Protocol accepts Nouns token voting power noncustodially via delegation, leveraging optimistic state to compensate registered Noun delegators with yield in exchange for delegating their voting power. The yield comprises the total funds raised by each Wave's winning ideas, which are represented as ERC1155 tokens.
Idea tokens that amass the highest capital from Sponsors are selected as winners at the conclusion of each Wave, the crowdfunding period during which ideas can be created and sponsored. The Wave Core contract determines the number of winning ideas per Wave and validates optimistic state at finalization based on its available "liquidity" (ie voting power) which it uses to push onchain proposals to the Nouns Governor.
Security Considerations
To run, Wave Protocol is designed to require only noncustodial delegation of the Nouns token's ERC721CheckPointable
voting power ledger, which is entirely separate from the token's ownership ledger. As a result, Wave Protocol never requires Nouns token approvals, transfers, or custody of any kind.
To provide voting power "liquidity" in exchange for yield, Nounder token holders need only lend their voting power by delegating and registering using the Wave UI and can rest assured that Wave Protocol does not ever touch the Nouns token's custodial ledger.
Why extend Nouns governance?
The Wave protocol introduces numerous benefits to all parties involved. It provides Nouns NFT holders with a way to earn yield on their Nouns tokens by noncustodially lending their voting power to the Wave protocol via delegation. Delegating to Wave thereby extends the right to make onchain proposals to addresses that don't hold Nouns tokens but would like to submit proposal ideas.
To run fuzz tests
$ forge test
Live Deployments
Wave protocol is currently live and deployed on Ethereum mainnet at the following contract addresses:
Name | Contract Details | Contract Address |
---|---|---|
IdeaTokenHub | Proxy | 0x000000000088b111eA8679dD42f7D55512fD6bE8 |
Wave | Proxy | 0x00000000008DDB753b2dfD31e7127f4094CE5630 |
WaveRenderer | Singleton | 0x65DBB4C59d4D5d279beec6dfdb169D986c55962C |
PolymathFont | Singleton | 0xf3A20995C9dD0F2d8e0DDAa738320F2C8871BD2b |
NounsToken | Dependency | 0x9C8fF314C9Bc7F6e59A9d9225Fb22946427eDC03 |
NounsGovernor | Dependency | 0x6f3E6272A167e8AcCb32072d08E0957F9c79223d |
Testnet deployments can be found in previous release tags (<= v1)
Overview
Wave Protocol accepts Nouns token voting power noncustodially via delegation, leveraging optimistic state to compensate registered Noun delegators with yield in exchange for delegating their voting power. The yield comprises the total funds raised by each Wave's winning ideas, which are represented as ERC1155 tokens.
Idea tokens that amass the highest capital from Sponsors are selected as winners at the conclusion of each Wave, the crowdfunding period during which ideas can be created and sponsored. The Wave Core contract determines the number of winning ideas per Wave and validates optimistic state at finalization based on its available "liquidity" (ie voting power) which it uses to push onchain proposals to the Nouns Governor.
Architecture Overview
Wave consists of three major parts: the IdeaTokenHub ERC1155 auction mechanism, the Wave Core contract, and Delegates which are used to push winning proposals to Nouns governance contracts.
- IdeaTokenHub
- Wave Core
- Delegates
IdeaTokenHub: Wave's hub of ERC1155 tokens representing ideas for Nouns proposals
The IdeaTokenHub handles tokenization and crowdfunding of permissionlessly submitted ideas for new Nouns governance proposals. Each idea is represented as a unique ERC1155 tokenId
, which enables permissionless on-chain sponsorship, ie lobbying via ERC1155 mint. Competition for pushing an idea token through to Nouns governance is introduced through a crowdfunding auction called a "Wave". At the conclusion of each Wave, tokenized ideas with the most funding are selected as winners and officially proposed into the Nouns governance system by leveraging the proposal power delegated to Wave Protocol by Nouns tokenholder LPs.
Liquidity Caveat: How many ideas per Wave win to become Nouns proposals?
Since Wave Protocol determines the number of winning ideas per Wave at finalization time, based on its available "liquidity" (ie voting power) which it uses to push onchain proposals to the Nouns Governor, the protocol must validate registered optimistic state at that time.
As a result, Wave Protocol has no control and no awareness of the Nouns token's voting power ledger other than at Wave finalization time. The permissionless nature of the Nouns token smart contract means Nounders may undelegate, transfer, sell, or burn during the interim period between Wave finalizations. To properly record expected liquidity in the Wave contracts and thereby compensate yield to the correct LPs, Nounders intending to provide voting power must optimistically register their voting power with Wave.
This means liquidity conditions at Wave finalization is subject to change up until the finalization transaction. To handle these externalities on the Nouns token contract, Wave's liquidity conditions are evaluated only at the moment of finalization. These liquidity conditions dictate the number of winning ideas for the current Wave and as a result the number of Nouns proposals pushed by a given Wave.
Wave Core: Central processing unit that coordinates Wave protocol entities
To perform official onchain proposals to Nouns governance, the Wave Core contract manages a set of deterministically derived Delegate contracts. These Delegate contracts are designed for one sole purpose: to non-custodially receive delegation from Noun token holders and push onchain proposals to the Nouns governance ecosystem. Nouns NFT holders who delegate to Wave are compensated for lending their voting power to the protocol (which grants it ability to create proposals on their behalf) in the form of yield earned from idea Sponsors who competitively lobby for turning ideas into Nouns proposals.
Delegation Caveats
One caveat worth noting is that since Nouns voting power delegation is all-or-nothing on an address basis, Noun token holders can only delegate (and earn yield) on Nouns token balances up to the proposal threshold per wallet address. At time of writing, this is 2 Nouns NFTs. Extra voting power of a Nounder LP beyond the proposal threshold (ie > 2 Nouns held by a delegator address) cannot be used by Wave Protocol
Furthermore, in order to enable Nounders' retension of the right to vote even while lending their voting power, registered delegations are handled optimistically and resolved at proposal time due to the fact that delegations can be revoked directly on the Nouns token contract. For Nounders who wish to vote on other proposals while still participating in Wave Protocol, see more information about undelegating from Wave to vote and then redelegating to collect yield.
Delegates: Proposal-pushers that interface with onchain Nouns governance contracts
All Wave Protocol Delegate contracts are deterministically derived via CREATE2 and their voting power liquidity is managed by the Wave Core. They are designed to receive Nouns token delegation non-custodially so they can be used as proxies to push onchain proposals to Nouns governance at Wave finalization.
For utmost security, Delegates never custody Nouns tokens and can only push proposals.
Protocol Participants Overview
User Flow
Wave Protocol participants can be split into three categories:
- Nouns Token LPs, who provide voting power liquidity to the protocol using the Nouns token's delegation ledger
- Idea Creators, who wish to contribute to the Nouns ecosystem with a worthy idea but lack the Nouns NFTs required to push a proposal onchain
- Idea Sponsors, who support existing ideas with funding in hopes that they win a Wave and are pushed onchain as a Nouns proposal (akin to lobbying)
Nouns holders
Nouns tokenholders must delegate and then register their voting power to Wave via a call to the Nouns token contract using either the delegate()
or delegateBySig()
function, while providing a valid Delegate address.
Note: Identifying a suitable delegate for a given Nounder based on their Nouns NFT token balance and based on Wave's current liquidity can be achieved using the Wave Core contract's getSuitableDelegateFor(address nounder)
view function.
Once voting power has been delegated to Wave, the tokenholder must register their delegation with Wave and thus their intent to provide proposal power. Registration updates this contract's storage to optimistically expect the registered voting power. Since delegation is performed directly on the Nouns token contract, this may change and is validated at the conclusion of each auction.
/// @dev Updates this contract's storage to reflect delegations performed directly on the Nouns token contract
function registerDelegation(address nounder, uint256 delegateId, uint256 numNouns) external;
Using ECDSA signatures, Nouns tokenholders can simultaneously create a delegate (if it doesn't yet exist) and grant voting power to the delegate in a single function call. Because the Nouns ERC721Checkpointable implementation only supports standard EOA ECDSA signatures, it thus does not support smart contract signatures. In that case, smart contract wallets holding Nouns NFTs must call delegate()
on the Nouns contract directly.
/// @dev Simultaneously creates a delegate if it doesn't yet exist and grants voting power to the delegate
function delegateBySig(WaveSignature calldata waveSig) external;
At the end of each wave, delegations deemed to have violated their optimistic registration are cleared and the remaining delegators whose voting power was legitimately provided to the protocol are marked eligible to claim their yield:
/// @dev Provides a way to collect the yield earned by Nounders who have delegated to Wave
function claim() external returns (uint256 claimAmt);
Idea Creators
Those who wish to submit a Nouns proposal idea for crowdfunding need simply to mint a new ERC1155 tokenId and provide a minimum funding amount.
/// @dev Creates a new ERC1155 token referred to by its token ID, ie its `ideaId` identifier
/// @notice To combat spam and low-quality proposals, idea token creation requires a small minimum payment
/// The Ether amount paid to create the idea will be reflected in the creator's ERC1155 balance in a 1:1 ratio
function createIdea(NounsDAOProposals.ProposalTxs calldata ideaTxs, string calldata description)
external
payable
returns (uint96 newIdeaId);
Idea Sponsors
Those who wish to sponsor (ie lobby for) an existing Nouns proposal idea to improve its chances of winning the wave's auction and be pushed to the Nouns governance contracts onchain may do so by providing a funding amount greater than the minimum:
/// @dev Sponsors the existing ERC1155 tokenized idea specified by its ID. The Ether amount paid
/// to create the idea will be reflected in the creator's ERC1155 balance in a 1:1 ratio
/// @notice To incentivize smooth protocol transitions and continued rollover of auction waves,
/// sponsorship attempts are reverted if the wave period has passed and `finalizeWave()` has not been executed
function sponsorIdea(uint256 ideaId) external payable;
IdeaTokenHub
Inherits: OwnableUpgradeable, UUPSUpgradeable, ERC1155Upgradeable, IIdeaTokenHub
Author: 📯📯📯.eth
State Variables
decimals
uint256 public constant decimals = 18;
__waveCore
IWave private __waveCore;
__nounsGovernor
INounsDAOLogicV4 private __nounsGovernor;
minSponsorshipAmount
ERC1155 balance recordkeeping directly mirrors Ether values
uint256 public minSponsorshipAmount;
waveLength
The length of time for a wave in blocks, marking the block number where winning ideas are chosen
uint256 public waveLength;
_currentWaveId
uint256 private _currentWaveId;
_nextIdeaId
uint96 private _nextIdeaId;
ideaInfos
type(uint96).max
size provides a large buffer for tokenIds, overflow is unrealistic
mapping(uint96 => IdeaInfo) internal ideaInfos;
waveInfos
mapping(uint256 => WaveInfo) internal waveInfos;
sponsorships
mapping(address => mapping(uint96 => SponsorshipParams)) internal sponsorships;
claimableYield
mapping(address => uint256) internal claimableYield;
Functions
constructor
constructor();
initialize
function initialize(
address owner_,
address nounsGovernor_,
uint256 minSponsorshipAmount_,
uint256 waveLength_,
string memory uri_
) external virtual initializer;
createIdea
To combat spam and low-quality proposals, idea token creation requires a small minimum payment The Ether amount paid to create the idea will be reflected in the creator's ERC1155 balance in a 1:1 ratio
Creates a new ERC1155 token referred to by its token ID, ie its ideaId
identifier
function createIdea(NounsDAOProposals.ProposalTxs calldata ideaTxs, string calldata description)
public
payable
returns (uint96 newIdeaId);
sponsorIdea
To incentivize smooth protocol transitions and continued rollover of auction waves,
sponsorship attempts are reverted if the wave period has passed and finalizeWave()
has not been executed
Sponsors the existing ERC1155 tokenized idea specified by its ID. The Ether amount paid to create the idea will be reflected in the creator's ERC1155 balance in a 1:1 ratio
function sponsorIdea(uint96 ideaId) public payable;
sponsorIdeaWithReason
To incentivize smooth protocol transitions and continued rollover of auction waves,
sponsorship attempts are reverted if the wave period has passed and finalizeWave()
has not been executed
Idential execution to sponsorIdea()
emitting a separate event with additional description string
function sponsorIdeaWithReason(uint96 ideaId, string calldata reason) public payable;
finalizeWave
To save gas on description
string SSTOREs, descriptions are stored offchain and their winning IDs must be re-validated in memory
Finalizes a Wave wave, marking the end of an auction wave. Winning ideas are selected by the highest
sponsored balances and officially proposed to the Nouns governance contracts. The number of winners varies
depending on the available 'liquidity' of lent Nouns NFTs and their proposal power. Yield distributions are
tallied by calling the Wave Core and recording valid delegations in the claimableYield
mapping where they
can then be claimed at any time by a Nouns holder who has delegated to Wave
function finalizeWave(uint96[] calldata offchainWinningIds, string[] calldata offchainDescriptions)
external
returns (IWave.Delegation[] memory delegations, uint256[] memory nounsProposalIds);
claim
Reentrance prevented via CEI
Provides a way to collect the yield earned by Nounders who have delegated to Wave for a full wave
function claim() external returns (uint256 claimAmt);
setMinSponsorshipAmount
Only callable by the owner
Sets the new minimum funding required to create and sponsor tokenized ideas
function setMinSponsorshipAmount(uint256 newMinSponsorshipAmount) external onlyOwner;
setWaveLength
Only callable by the owner
Sets the new length of Wave waves in blocks
function setWaveLength(uint256 newWavelength) external onlyOwner;
getWinningIdeaIds
Intended for offchain usage to aid in fetching offchain description
string data before calling finalizeWave()
If a full list of eligible IdeaIds ordered by current funding is desired, use getOrderedEligibleIdeaIds(0)
instead
Returns an array of the current wave's leading IdeaIds where the array length is determined by the protocol's number of available proposer delegates, fetched from the WaveCore contract
function getWinningIdeaIds()
public
view
returns (uint256 minRequiredVotes, uint256 numEligibleProposers, uint96[] memory winningIds);
getOrderedEligibleIdeaIds
The returned array treats ineligible IDs (ie already proposed) as 0 values at the array end.
Since 0 is an invalid ideaId
, these are filtered out when invoked by finalizeWave()
and getWinningIdeaIds()
Fetches an array of ideaIds
eligible for proposal, ordered by total funding
function getOrderedEligibleIdeaIds(uint256 optLimiter) public view returns (uint96[] memory orderedEligibleIds);
Parameters
Name | Type | Description |
---|---|---|
optLimiter | uint256 | An optional limiter used to define the number of desired ideaIds , for example the number of eligible proposers or winning ids. If provided, it will be used to define the length of the returned array |
getOrderedProposedIdeaIds
Intended for external use for improved devX
Returns IDs of ideas which have already won waves and been proposed to Nouns governance
function getOrderedProposedIdeaIds() public view returns (uint96[] memory orderedProposedIds);
getIdeaInfo
Returns the IdeaInfo struct associated with a given ideaId
function getIdeaInfo(uint256 ideaId) external view returns (IdeaInfo memory);
getParentWaveId
Returns the waveId
representing the parent Wave during which the given ideaId
was created
function getParentWaveId(uint256 ideaId) external view returns (uint256 waveId);
getSponsorshipInfo
Returns the SponsorshipParams struct associated with a given sponsor
address and ideaId
function getSponsorshipInfo(address sponsor, uint256 ideaId) public view returns (SponsorshipParams memory);
getClaimableYield
Returns the funds available to claim for a Nounder who has delegated to Wave
function getClaimableYield(address nounder) external view returns (uint256);
getOptimisticYieldEstimate
Returned estimate is based on optimistic state and is subject to change until Wave finalization
Returns an estimate of expected yield for the given Nounder LP who has delegated voting power to Wave
function getOptimisticYieldEstimate(address nounder) external view returns (uint256 yieldEstimate);
getWaveInfo
Returns information pertaining to the given Wave ID as a WaveInfo
struct
function getWaveInfo(uint256 waveId) public view returns (WaveInfo memory);
getCurrentWaveInfo
Returns information pertaining to the current Wave as a WaveInfo
struct
function getCurrentWaveInfo() external view returns (uint256 currentWaveId, WaveInfo memory currentWaveInfo);
getNextIdeaId
Returns the next ideaId
which makes use of the tokenId
mechanic from the ERC1155 standard
function getNextIdeaId() public view returns (uint256);
_validateIdeaCreation
function _validateIdeaCreation(NounsDAOProposals.ProposalTxs calldata _ideaTxs, string calldata _description)
internal;
_sponsorIdea
function _sponsorIdea(uint96 _ideaId) internal;
_updateWaveState
function _updateWaveState() internal returns (uint256 previousWaveId);
_processWinningIdeas
function _processWinningIdeas(
uint96[] calldata _offchainWinningIds,
string[] calldata _offchainDescriptions,
uint96[] memory _winningIds
)
internal
returns (
uint256 winningProposalsTotalFunding,
IWave.Proposal[] memory winningProposals,
ProposalInfo[] memory proposedIdeas
);
_beforeTokenTransfer
function _beforeTokenTransfer(
address operator,
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual override;
_authorizeUpgrade
function _authorizeUpgrade(address) internal virtual override;
IdeaTokenHub
Inherits: OwnableUpgradeable, UUPSUpgradeable, ERC1155Upgradeable, IIdeaTokenHub
Author: 📯📯📯.eth
State Variables
decimals
uint256 public constant decimals = 18;
__waveCore
IWave private __waveCore;
__nounsGovernor
INounsDAOLogicV4 private __nounsGovernor;
minSponsorshipAmount
ERC1155 balance recordkeeping directly mirrors Ether values
uint256 public minSponsorshipAmount;
waveLength
The length of time for a wave in blocks, marking the block number where winning ideas are chosen
uint256 public waveLength;
_currentWaveId
uint256 private _currentWaveId;
_nextIdeaId
uint96 private _nextIdeaId;
ideaInfos
type(uint96).max
size provides a large buffer for tokenIds, overflow is unrealistic
mapping(uint96 => IdeaInfo) internal ideaInfos;
waveInfos
mapping(uint256 => WaveInfo) internal waveInfos;
sponsorships
mapping(address => mapping(uint96 => SponsorshipParams)) internal sponsorships;
claimableYield
mapping(address => uint256) internal claimableYield;
Functions
constructor
constructor();
initialize
function initialize(
address owner_,
address nounsGovernor_,
uint256 minSponsorshipAmount_,
uint256 waveLength_,
string memory uri_
) external virtual initializer;
createIdea
To combat spam and low-quality proposals, idea token creation requires a small minimum payment The Ether amount paid to create the idea will be reflected in the creator's ERC1155 balance in a 1:1 ratio
Creates a new ERC1155 token referred to by its token ID, ie its ideaId
identifier
function createIdea(NounsDAOProposals.ProposalTxs calldata ideaTxs, string calldata description)
public
payable
returns (uint96 newIdeaId);
sponsorIdea
To incentivize smooth protocol transitions and continued rollover of auction waves,
sponsorship attempts are reverted if the wave period has passed and finalizeWave()
has not been executed
Sponsors the existing ERC1155 tokenized idea specified by its ID. The Ether amount paid to create the idea will be reflected in the creator's ERC1155 balance in a 1:1 ratio
function sponsorIdea(uint96 ideaId) public payable;
sponsorIdeaWithReason
To incentivize smooth protocol transitions and continued rollover of auction waves,
sponsorship attempts are reverted if the wave period has passed and finalizeWave()
has not been executed
Idential execution to sponsorIdea()
emitting a separate event with additional description string
function sponsorIdeaWithReason(uint96 ideaId, string calldata reason) public payable;
finalizeWave
To save gas on description
string SSTOREs, descriptions are stored offchain and their winning IDs must be re-validated in memory
Finalizes a Wave wave, marking the end of an auction wave. Winning ideas are selected by the highest
sponsored balances and officially proposed to the Nouns governance contracts. The number of winners varies
depending on the available 'liquidity' of lent Nouns NFTs and their proposal power. Yield distributions are
tallied by calling the Wave Core and recording valid delegations in the claimableYield
mapping where they
can then be claimed at any time by a Nouns holder who has delegated to Wave
function finalizeWave(uint96[] calldata offchainWinningIds, string[] calldata offchainDescriptions)
external
returns (IWave.Delegation[] memory delegations, uint256[] memory nounsProposalIds);
claim
Reentrance prevented via CEI
Provides a way to collect the yield earned by Nounders who have delegated to Wave for a full wave
function claim() external returns (uint256 claimAmt);
setMinSponsorshipAmount
Only callable by the owner
Sets the new minimum funding required to create and sponsor tokenized ideas
function setMinSponsorshipAmount(uint256 newMinSponsorshipAmount) external onlyOwner;
setWaveLength
Only callable by the owner
Sets the new length of Wave waves in blocks
function setWaveLength(uint256 newWavelength) external onlyOwner;
getWinningIdeaIds
Intended for offchain usage to aid in fetching offchain description
string data before calling finalizeWave()
If a full list of eligible IdeaIds ordered by current funding is desired, use getOrderedEligibleIdeaIds(0)
instead
Returns an array of the current wave's leading IdeaIds where the array length is determined by the protocol's number of available proposer delegates, fetched from the WaveCore contract
function getWinningIdeaIds()
public
view
returns (uint256 minRequiredVotes, uint256 numEligibleProposers, uint96[] memory winningIds);
getOrderedEligibleIdeaIds
The returned array treats ineligible IDs (ie already proposed) as 0 values at the array end.
Since 0 is an invalid ideaId
, these are filtered out when invoked by finalizeWave()
and getWinningIdeaIds()
Fetches an array of ideaIds
eligible for proposal, ordered by total funding
function getOrderedEligibleIdeaIds(uint256 optLimiter) public view returns (uint96[] memory orderedEligibleIds);
Parameters
Name | Type | Description |
---|---|---|
optLimiter | uint256 | An optional limiter used to define the number of desired ideaIds , for example the number of eligible proposers or winning ids. If provided, it will be used to define the length of the returned array |
getOrderedProposedIdeaIds
Intended for external use for improved devX
Returns IDs of ideas which have already won waves and been proposed to Nouns governance
function getOrderedProposedIdeaIds() public view returns (uint96[] memory orderedProposedIds);
getIdeaInfo
Returns the IdeaInfo struct associated with a given ideaId
function getIdeaInfo(uint256 ideaId) external view returns (IdeaInfo memory);
getParentWaveId
Returns the waveId
representing the parent Wave during which the given ideaId
was created
function getParentWaveId(uint256 ideaId) external view returns (uint256 waveId);
getSponsorshipInfo
Returns the SponsorshipParams struct associated with a given sponsor
address and ideaId
function getSponsorshipInfo(address sponsor, uint256 ideaId) public view returns (SponsorshipParams memory);
getClaimableYield
Returns the funds available to claim for a Nounder who has delegated to Wave
function getClaimableYield(address nounder) external view returns (uint256);
getOptimisticYieldEstimate
Returned estimate is based on optimistic state and is subject to change until Wave finalization
Returns an estimate of expected yield for the given Nounder LP who has delegated voting power to Wave
function getOptimisticYieldEstimate(address nounder) external view returns (uint256 yieldEstimate);
getWaveInfo
Returns information pertaining to the given Wave ID as a WaveInfo
struct
function getWaveInfo(uint256 waveId) public view returns (WaveInfo memory);
getCurrentWaveInfo
Returns information pertaining to the current Wave as a WaveInfo
struct
function getCurrentWaveInfo() external view returns (uint256 currentWaveId, WaveInfo memory currentWaveInfo);
getNextIdeaId
Returns the next ideaId
which makes use of the tokenId
mechanic from the ERC1155 standard
function getNextIdeaId() public view returns (uint256);
_validateIdeaCreation
function _validateIdeaCreation(NounsDAOProposals.ProposalTxs calldata _ideaTxs, string calldata _description)
internal;
_sponsorIdea
function _sponsorIdea(uint96 _ideaId) internal;
_updateWaveState
function _updateWaveState() internal returns (uint256 previousWaveId);
_processWinningIdeas
function _processWinningIdeas(
uint96[] calldata _offchainWinningIds,
string[] calldata _offchainDescriptions,
uint96[] memory _winningIds
)
internal
returns (
uint256 winningProposalsTotalFunding,
IWave.Proposal[] memory winningProposals,
ProposalInfo[] memory proposedIdeas
);
_beforeTokenTransfer
function _beforeTokenTransfer(
address operator,
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual override;
_authorizeUpgrade
function _authorizeUpgrade(address) internal virtual override;
Wave
Inherits: Ownable, UUPSUpgradeable, IWave
Author: 📯📯📯.eth
State Variables
nounsGovernor
INounsDAOLogicV4 public nounsGovernor;
nounsToken
IERC721Checkpointable public nounsToken;
__creationCodeHash
bytes32 private __creationCodeHash;
ideaTokenHub
IIdeaTokenHub public ideaTokenHub;
_optimisticDelegations
Since delegations can be revoked directly on the Nouns token contract, active delegations are handled optimistically
Delegation[] private _optimisticDelegations;
_nextDelegateId
Declared as uint16
type to efficiently pack into storage structs, but used as uint256
or bytes32
when used as part of create2
deployment or other function parameter
Identifier used to derive and refer to the address of Delegate proxy contracts
uint16 private _nextDelegateId;
Functions
constructor
constructor();
initialize
function initialize(
address ideaTokenHub_,
address nounsGovernor_,
address nounsToken_,
uint256 minSponsorshipAmount_,
uint256 waveLength_,
string memory uri
) public virtual initializer;
pushProposals
May only be called by the Wave's ERC1155 Idea token hub at the conclusion of each 2-week wave
Pushes the winning proposal onto the nounsGovernor
to be voted on in the Nouns governance ecosystem
Checks for changes in delegation state on nounsToken
contract and updates Wave recordkeeping accordingly
function pushProposals(IWave.Proposal[] calldata winningProposals)
public
payable
returns (IWave.Delegation[] memory delegations, uint256[] memory nounsProposalIds);
delegateBySig
The Nouns ERC721Checkpointable implementation only supports standard EOA ECDSA signatures and thus
does not support smart contract signatures. In that case, delegate()
must be called on the Nouns contract directly
Simultaneously creates a delegate if it doesn't yet exist and grants voting power to the delegate in a single function call. This is the most convenient option for standard wallets using EOA private keys
function delegateBySig(WaveSignature calldata waveSig) external;
registerDelegation
Delegation to must have been performed via a call to the Nouns token contract using either the
delegate()
or delegateBySig()
function, having provided the correct Delegate address for the given ID
Updates this contract's storage to reflect delegations performed directly on the Nouns token contract
function registerDelegation(address nounder, uint256 delegateId) external;
createDelegate
As the constructor argument is appended to bytecode, it affects resulting address, eliminating risk of DOS vector
Deploys a Delegate contract deterministically via create2
, using the _nextDelegateId
as salt
function createDelegate() public returns (address delegate);
getDelegateAddress
Computes the counterfactual address for a given delegate ID whether or not it has been deployed
function getDelegateAddress(uint256 delegateId) public view returns (address delegate);
getDelegateId
Intended for offchain devX convenience only; not used in a write capacity within protocol
Returns the delegateId
for a given delegate address by iterating over existing delegates to find a match
function getDelegateId(address delegate) external view returns (uint256 delegateId);
getDelegateIdByType
Returns either an existing delegate ID if one meets the given parameters, otherwise returns the next delegate ID
function getDelegateIdByType(uint256 minRequiredVotes, bool isSupplementary) public view returns (uint256 delegateId);
Parameters
Name | Type | Description |
---|---|---|
minRequiredVotes | uint256 | Minimum votes to make a proposal. Must be more than current proposal threshold which is based on Nouns token supply |
isSupplementary | bool | Whether or not to search for a Delegate that doesn't meet the current proposal threshold |
Returns
Name | Type | Description |
---|---|---|
delegateId | uint256 | The ID of a delegate that matches the given criteria |
getNextDelegateId
Typecasts and returns the next delegate ID as a uint256
function getNextDelegateId() public view returns (uint256 nextDelegateId);
getSuitableDelegateFor
Returns a suitable delegate address for an account based on its voting power
function getSuitableDelegateFor(address nounder) external view returns (address delegate, uint256 minRequiredVotes);
getCurrentMinRequiredVotes
Returns the current minimum votes required to submit an onchain proposal to Nouns governance
function getCurrentMinRequiredVotes() public view returns (uint256 minRequiredVotes);
getAllPartialDelegates
Returns all existing Delegates with voting power below the minimum required to make a proposal Provided to improve offchain devX; returned values can change at any time as Nouns ecosystem is external
function getAllPartialDelegates() external view returns (uint256 minRequiredVotes, address[] memory partialDelegates);
numEligibleProposerDelegates
Returns the number of existing Delegates currently eligible to make a proposal
function numEligibleProposerDelegates() public view returns (uint256 minRequiredVotes, uint256 numEligibleProposers);
getAllEligibleProposerDelegates
Returns all existing Delegates currently eligible for making a proposal Provided to improve offchain devX: returned values can change at any time as Nouns ecosystem is external
function getAllEligibleProposerDelegates()
public
view
returns (uint256 minRequiredVotes, uint256[] memory eligibleProposerIds);
getOptimisticDelegations
Delegation array in storage is optimistic and should never be relied on externally
function getOptimisticDelegations() public view returns (Delegation[] memory);
computeNounsDelegationDigest
Convenience function to facilitate offchain development by computing the delegateBySig()
digest
for a given signer and expiry
function computeNounsDelegationDigest(address signer, uint256 delegateId, uint256 expiry)
public
view
returns (bytes32 digest);
_findDelegateId
Returns the id of the first delegate ID found to meet the given parameters To save gas by minimizing costly SLOADs, terminates as soon as a delegate meeting the critera is found
function _findDelegateId(uint256 _minRequiredVotes, bool _isSupplementary) internal view returns (uint256 delegateId);
Parameters
Name | Type | Description |
---|---|---|
_minRequiredVotes | uint256 | The votes needed to make a proposal, dynamic based on Nouns token supply |
_isSupplementary | bool | Whether or not the returned Delegate should accept fewer than required votes |
_disqualifiedDelegationIndices
Returns an array of delegation IDs that violated the protocol rules and are ineligible for yield
function _disqualifiedDelegationIndices() internal view returns (uint256[] memory);
_isDisqualified
Returns true for delegations that violated their optimistically registered parameters
function _isDisqualified(address _nounder, address _delegate, uint256 _votingPower)
internal
view
returns (bool _disqualify);
_deleteDelegations
Deletes Delegations by swapping the non-final index members to be removed with members to be preserved
function _deleteDelegations(uint256[] memory _indices) internal;
_sortIndicesDescending
Sorts array of indices to be deleted in descending order so the remaining indexes are not disturbed via resizing
function _sortIndicesDescending(uint256[] memory _indices) internal pure returns (uint256[] memory);
_setOptimisticDelegation
function _setOptimisticDelegation(Delegation memory _delegation) internal;
_checkForActiveProposal
Returns true when an active proposal exists for the delegate, meaning it is ineligible to propose
function _checkForActiveProposal(address delegate) internal view returns (bool _noActiveProp);
_isEligibleProposalState
References the Nouns governor contract to determine whether a proposal is in a disqualifying state
function _isEligibleProposalState(uint256 _latestProposal) internal view returns (bool);
_simulateCreate2
Computes a counterfactual Delegate address via create2
using its creation code and delegateId
as salt
function _simulateCreate2(bytes32 _salt, bytes32 _creationCodeHash)
internal
view
returns (address simulatedDeployment);
_authorizeUpgrade
function _authorizeUpgrade(address) internal virtual override;
Delegate
Author: 📯📯📯.eth
All Wave Protocol Delegate contracts are managed by the Wave Core. They are designed to receive Nouns token delegation non-custodially so they can be used as proxies to push onchain proposals to Nouns governance.
For utmost security, Delegates never custody Nouns tokens and can only push proposals
State Variables
waveCore
address public immutable waveCore;
Functions
constructor
constructor(address waveCore_);
pushProposal
function pushProposal(
INounsDAOLogicV4 governor,
NounsDAOProposals.ProposalTxs calldata txs,
string calldata description
) external returns (uint256 nounsProposalId);
Errors
NotWaveCore
error NotWaveCore(address caller);
Contents
IERC721Checkpointable
Interface for interacting with the Nouns ERC721 governance token with minimal deployment bytecode overhead
Functions
name
Returns the name of the ERC721 token
function name() external view returns (string memory);
decimals
Defines decimals as per ERC-20 convention to make integrations with 3rd party governance platforms easier
function decimals() external returns (uint8);
checkpoints
A record of votes checkpoints for each account, by index
function checkpoints(address account, uint32 index) external view returns (Checkpoint memory);
numCheckpoints
The number of checkpoints for each account
function numCheckpoints(address account) external returns (uint32);
DOMAIN_TYPEHASH
The EIP-712 typehash for the contract's domain
function DOMAIN_TYPEHASH() external view returns (bytes32);
DELEGATION_TYPEHASH
The EIP-712 typehash for the delegation struct used by the contract
function DELEGATION_TYPEHASH() external view returns (bytes32);
nonces
A record of states for signing / validating signatures
function nonces(address account) external view returns (uint256);
votesToDelegate
The votes a delegator can delegate, which is the current balance of the delegator.
function votesToDelegate(address delegator) external view returns (uint96);
delegates
Overrides the standard Comp.sol
delegates mapping to return delegator's own address if they haven't delegated.
function delegates(address delegator) external view returns (address);
delegate
Delegate votes from msg.sender
to delegatee
function delegate(address delegatee) external;
delegateBySig
Delegates votes from signatory to delegatee
function delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) external;
getCurrentVotes
Gets the current votes balance for account
function getCurrentVotes(address account) external view returns (uint96);
getPriorVotes
Determine the prior number of votes for an account as of a block number
function getPriorVotes(address account, uint256 blockNumber) external view returns (uint96);
Structs
Checkpoint
A checkpoint for marking number of votes from a given block
struct Checkpoint {
uint32 fromBlock;
uint96 votes;
}
IIdeaTokenHub
Interface for interacting with the Wave IdeaTokenHub contract which manages tokenized ideas via ERC1155
Functions
minSponsorshipAmount
function minSponsorshipAmount() external view returns (uint256);
decimals
function decimals() external view returns (uint256);
waveLength
function waveLength() external view returns (uint256);
initialize
function initialize(
address owner_,
address nounsGovernor_,
uint256 minSponsorshipAmount_,
uint256 waveLength_,
string memory uri_
) external;
createIdea
To combat spam and low-quality proposals, idea token creation requires a small minimum payment The Ether amount paid to create the idea will be reflected in the creator's ERC1155 balance in a 1:1 ratio
Creates a new ERC1155 token referred to by its token ID, ie its ideaId
identifier
function createIdea(NounsDAOProposals.ProposalTxs calldata ideaTxs, string calldata description)
external
payable
returns (uint96 newIdeaId);
sponsorIdea
To incentivize smooth protocol transitions and continued rollover of auction waves,
sponsorship attempts are reverted if the wave period has passed and finalizeWave()
has not been executed
Sponsors the existing ERC1155 tokenized idea specified by its ID. The Ether amount paid to create the idea will be reflected in the creator's ERC1155 balance in a 1:1 ratio
function sponsorIdea(uint96 ideaId) external payable;
sponsorIdeaWithReason
To incentivize smooth protocol transitions and continued rollover of auction waves,
sponsorship attempts are reverted if the wave period has passed and finalizeWave()
has not been executed
Idential execution to sponsorIdea()
emitting a separate event with additional description string
function sponsorIdeaWithReason(uint96 ideaId, string calldata reason) external payable;
finalizeWave
Finalizes a Wave wave, marking the end of an auction wave. Winning ideas are selected by the highest
sponsored balances and officially proposed to the Nouns governance contracts. The number of winners varies
depending on the available 'liquidity' of lent Nouns NFTs and their proposal power. Yield distributions are
tallied by calling the Wave Core and recording valid delegations in the claimableYield
mapping where they
can then be claimed at any time by a Nouns holder who has delegated to Wave
function finalizeWave(uint96[] calldata offchainWinningIds, string[] calldata offchainDescriptions)
external
returns (IWave.Delegation[] memory delegations, uint256[] memory nounsProposalIds);
claim
Reentrance prevented via CEI
Provides a way to collect the yield earned by Nounders who have delegated to Wave for a full wave
function claim() external returns (uint256 claimAmt);
setMinSponsorshipAmount
Only callable by the owner
Sets the new minimum funding required to create and sponsor tokenized ideas
function setMinSponsorshipAmount(uint256 newMinSponsorshipAmount) external;
setWaveLength
Only callable by the owner
Sets the new length of Wave waves in blocks
function setWaveLength(uint256 newWavelength) external;
getWinningIdeaIds
Returns an array of the current wave's leading IdeaIds where the array length is determined by the protocol's number of available proposer delegates, fetched from the WaveCore contract
function getWinningIdeaIds()
external
view
returns (uint256 minRequiredVotes, uint256 numEligibleProposers, uint96[] memory winningIds);
getOrderedEligibleIdeaIds
Fetches an array of ideaIds
eligible for proposal, ordered by total funding
function getOrderedEligibleIdeaIds(uint256 optLimiter) external view returns (uint96[] memory orderedEligibleIds);
Parameters
Name | Type | Description |
---|---|---|
optLimiter | uint256 | An optional limiter used to define the number of desired ideaIds , for example the number of eligible proposers or winning ids. If provided, it will be used to define the length of the returned array |
getOrderedProposedIdeaIds
Intended for external use for improved devX
Returns IDs of ideas which have already won waves and been proposed to Nouns governance
function getOrderedProposedIdeaIds() external view returns (uint96[] memory orderedProposedIds);
getIdeaInfo
Returns the IdeaInfo struct associated with a given ideaId
function getIdeaInfo(uint256 ideaId) external view returns (IdeaInfo memory);
getSponsorshipInfo
Returns the SponsorshipParams struct associated with a given sponsor
address and ideaId
function getSponsorshipInfo(address sponsor, uint256 ideaId) external view returns (SponsorshipParams memory);
getClaimableYield
Returns the funds available to claim for a Nounder who has delegated to Wave
function getClaimableYield(address nounder) external view returns (uint256);
getOptimisticYieldEstimate
Returned estimate is based on optimistic state and is subject to change until Wave finalization
Returns an estimate of expected yield for the given Nounder LP who has delegated voting power to Wave
function getOptimisticYieldEstimate(address nounder) external view returns (uint256 yieldEstimate);
getWaveInfo
Returns information pertaining to the given Wave ID as a WaveInfo
struct
function getWaveInfo(uint256 waveId) external view returns (WaveInfo memory);
getCurrentWaveInfo
Returns information pertaining to the current Wave as a WaveInfo
struct
function getCurrentWaveInfo() external view returns (uint256 currentWaveId, WaveInfo memory currentWaveInfo);
getParentWaveId
Returns the waveId
representing the parent Wave during which the given ideaId
was created
function getParentWaveId(uint256 ideaId) external view returns (uint256 waveId);
getNextIdeaId
Returns the next ideaId
which makes use of the tokenId
mechanic from the ERC1155 standard
function getNextIdeaId() external view returns (uint256);
Events
IdeaCreated
event IdeaCreated(IWave.Proposal idea, address creator, uint96 ideaId, SponsorshipParams params);
Sponsorship
event Sponsorship(address sponsor, uint96 ideaId, SponsorshipParams params, string reason);
WaveFinalized
event WaveFinalized(ProposalInfo[] proposedIdeas, WaveInfo previousWaveInfo);
Errors
BelowMinimumSponsorshipAmount
error BelowMinimumSponsorshipAmount(uint256 value);
InvalidActionsCount
error InvalidActionsCount(uint256 count);
ProposalInfoArityMismatch
error ProposalInfoArityMismatch();
InvalidOffchainDataProvided
error InvalidOffchainDataProvided();
InvalidDescription
error InvalidDescription();
NonexistentIdeaId
error NonexistentIdeaId(uint256 ideaId);
AlreadyProposed
error AlreadyProposed(uint256 ideaId);
WaveIncomplete
error WaveIncomplete();
ClaimFailure
error ClaimFailure();
Soulbound
error Soulbound();
Structs
WaveInfo
struct WaveInfo {
uint32 startBlock;
uint32 endBlock;
}
IdeaInfo
struct IdeaInfo {
uint216 totalFunding;
uint32 blockCreated;
bool isProposed;
NounsDAOProposals.ProposalTxs proposalTxs;
}
SponsorshipParams
struct SponsorshipParams {
uint216 contributedBalance;
bool isCreator;
}
ProposalInfo
struct ProposalInfo {
uint256 nounsProposalId;
uint256 waveIdeaId;
uint216 totalFunding;
uint32 blockCreated;
}
INounsDAOLogicV4
IWave
Interface for interacting with the Wave protocol core contract
Functions
initialize
function initialize(
address ideaTokenHub_,
address nounsGovernor_,
address nounsToken_,
uint256 minSponsorshipAmount_,
uint256 waveLength_,
string memory uri
) external;
pushProposals
May only be called by the Wave's ERC1155 Idea token hub at the conclusion of each 2-week wave
Pushes the winning proposal onto the nounsGovernor
to be voted on in the Nouns governance ecosystem
Checks for changes in delegation state on nounsToken
contract and updates Wave recordkeeping accordingly
function pushProposals(Proposal[] calldata winningProposals)
external
payable
returns (Delegation[] memory delegations, uint256[] memory nounsProposalIds);
delegateBySig
The Nouns ERC721Checkpointable implementation only supports standard EOA ECDSA signatures and thus
does not support smart contract signatures. In that case, delegate()
must be called on the Nouns contract directly
Simultaneously creates a delegate if it doesn't yet exist and grants voting power to the delegate in a single function call. This is the most convenient option for standard wallets using EOA private keys
function delegateBySig(WaveSignature calldata waveSig) external;
registerDelegation
Delegation to must have been performed via a call to the Nouns token contract using either the
delegate()
or delegateBySig()
function, having provided the correct Delegate address for the given ID
Updates this contract's storage to reflect delegations performed directly on the Nouns token contract
Serves as an alternative to delegateByDelegatecall()
for smart contract wallets
function registerDelegation(address nounder, uint256 delegateId) external;
createDelegate
As the constructor argument is appended to bytecode, it affects resulting address, eliminating risk of DOS vector
Deploys a Delegate contract deterministically via create2
, using the _nextDelegateId
as salt
function createDelegate() external returns (address delegate);
getDelegateAddress
Computes the counterfactual address for a given delegate ID whether or not it has been deployed
function getDelegateAddress(uint256 delegateId) external view returns (address delegate);
getDelegateId
Intended for offchain devX convenience only; not used in a write capacity within protocol
Returns the delegateId
for a given delegate address by iterating over existing delegates to find a match
function getDelegateId(address delegate) external view returns (uint256 delegateId);
getDelegateIdByType
Returns either an existing delegate ID if one meets the given parameters, otherwise returns the next delegate ID
function getDelegateIdByType(uint256 minRequiredVotes, bool isSupplementary)
external
view
returns (uint256 delegateId);
Parameters
Name | Type | Description |
---|---|---|
minRequiredVotes | uint256 | Minimum votes to make a proposal. Must be more than current proposal threshold which is based on Nouns token supply |
isSupplementary | bool | Whether or not to search for a Delegate that doesn't meet the current proposal threshold |
Returns
Name | Type | Description |
---|---|---|
delegateId | uint256 | The ID of a delegate that matches the given criteria |
getNextDelegateId
Typecasts and returns the next delegate ID as a uint256
function getNextDelegateId() external view returns (uint256 nextDelegateId);
getSuitableDelegateFor
Returns a suitable delegate address for an account based on its voting power
function getSuitableDelegateFor(address nounder) external view returns (address delegate, uint256 minRequiredVotes);
getCurrentMinRequiredVotes
Returns the current minimum votes required to submit an onchain proposal to Nouns governance
function getCurrentMinRequiredVotes() external view returns (uint256 minRequiredVotes);
getAllPartialDelegates
Returns all existing Delegates with voting power below the minimum required to make a proposal Provided to improve offchain devX; returned values can change at any time as Nouns ecosystem is external
function getAllPartialDelegates() external view returns (uint256 minRequiredVotes, address[] memory partialDelegates);
numEligibleProposerDelegates
Returns the number of existing Delegates currently eligible to make a proposal
function numEligibleProposerDelegates()
external
view
returns (uint256 minRequiredVotes, uint256 numEligibleProposers);
getAllEligibleProposerDelegates
Returns all existing Delegates currently eligible for making a proposal Provided to improve offchain devX: returned values can change at any time as Nouns ecosystem is external
function getAllEligibleProposerDelegates()
external
view
returns (uint256 minRequiredVotes, uint256[] memory eligibleProposerIds);
getOptimisticDelegations
Returns optimistic delegations from storage. These are subject to change and should never be relied upon
function getOptimisticDelegations() external view returns (Delegation[] memory);
computeNounsDelegationDigest
Convenience function to facilitate offchain development by computing the delegateBySig()
digest
for a given signer and expiry
function computeNounsDelegationDigest(address signer, uint256 delegateId, uint256 expiry)
external
view
returns (bytes32 digest);
Events
DelegateCreated
event DelegateCreated(address delegate, uint256 id);
DelegationRegistered
event DelegationRegistered(Delegation optimisticDelegation);
DelegationDeleted
event DelegationDeleted(Delegation disqualifiedDelegation);
Errors
Unauthorized
error Unauthorized();
InsufficientDelegations
error InsufficientDelegations();
NotDelegated
error NotDelegated(address nounder, address delegate);
InsufficientVotingPower
error InsufficientVotingPower(address nounder);
DelegateSaturated
error DelegateSaturated(uint256 delegateId);
InvalidDelegateId
error InvalidDelegateId(uint256 delegateId);
InvalidDelegateAddress
error InvalidDelegateAddress(address delegate);
InvalidSignature
error InvalidSignature();
OnlyDelegatecallContext
error OnlyDelegatecallContext();
Create2Failure
error Create2Failure();
Structs
Delegation
struct Delegation {
address delegator;
uint32 blockDelegated;
uint16 votingPower;
uint16 delegateId;
}
Properties
Name | Type | Description |
---|---|---|
delegator | address | Only token holder addresses are stored since Delegates can be derived |
blockDelegated | uint32 | Block at which a Noun was delegated, used for payout calculation. Only records delegations performed via this contract, ie not direct delegations on Nouns token |
votingPower | uint16 | Voting power can safely be stored in a uint16 as the type's maximum represents 179.5 years of Nouns token supply issuance (at a rate of one per day) |
delegateId | uint16 |
WaveSignature
struct WaveSignature {
address signer;
uint256 delegateId;
uint256 numNouns;
uint256 nonce;
uint256 expiry;
bytes signature;
}
Proposal
struct Proposal {
NounsDAOProposals.ProposalTxs ideaTxs;
string description;
}
Protocol Usage and Development
Write Functions: Interacting with the protocol
To best understand the mechanisms of Wave Protocol's write functions, please refer directly to the NatSpec documentation within the external-facing contract interfaces.
These include the IdeaTokenHub Interface Contract as well as the Wave Core Interface Contract.
View Functions: Reading and querying the protocol
The Wave protocol core contract provides numerous convenience functions to improve offchain devX by returning values relevant for developing offchain components.
To view the current minimum votes required to submit an onchain proposal to Nouns governance
function getCurrentMinRequiredVotes() external view returns (uint256 minRequiredVotes);
To fetch the delegateId
for a given delegate address
function getDelegateId(address delegate) external view returns (uint256 delegateId);
To fetch a suitable Wave delegate for a given user based on their Nouns token voting power. This is the address the tokenholder should delegate to, using the Nouns token contract delegate()
function.
/// @dev Returns a suitable delegate address for an account based on its voting power
function getSuitableDelegateFor(address nounder)
external
view
returns (address delegate, uint256 minRequiredVotes);
To search for an available delegate of a given type:
/// @dev Returns either an existing delegate ID if one meets the given parameters, otherwise returns the next delegate ID
/// @param isSupplementary Whether or not to search for a Delegate that doesn't meet the current proposal threshold
/// @param minRequiredVotes Minimum votes to make a proposal. Must be more than current proposal threshold which is based on Nouns token supply
/// @return delegateId The ID of a delegate that matches the given criteria
function getDelegateIdByType(uint256 minRequiredVotes, bool isSupplementary)
external
view
returns (uint256 delegateId);
To view all existing "partial" Delegates, ie ones with voting power below the minimum required to make a proposal
function getAllPartialDelegates()
external
view
returns (uint256 minRequiredVotes, address[] memory partialDelegates);
To get the number of currently expected winning ideas- ie the number of Delegates that are currently eligible to propose
/// @dev Returns the number of existing Delegates currently eligible to make a proposal
function numEligibleProposerDelegates()
external
view
returns (uint256 minRequiredVotes, uint256 numEligibleProposers);
To view all existing Delegates that currently possess enough delegated voting power to push a Nouns proposal
/// @dev Returns all existing Delegates currently eligible for making a proposal
function getAllEligibleProposerDelegates()
external
view
returns (uint256 minRequiredVotes, uint256[] memory eligibleProposerIds);
The IdeaTokenHub likewise provides several convenience functions, some of which are listed below:
To view all existing IdeaIds that are eligible for proposal sorted by funding
/// @param optLimiter An optional limiter used to define the number of desired `ideaIds`, for example the number of
/// eligible proposers or winning ids. If provided, it will be used to define the length of the returned array
function getOrderedEligibleIdeaIds(uint256 optLimiter) external view returns (uint96[] memory orderedEligibleIds);
To view the leading IdeaIds which are expected to win this wave and be proposed to Nouns governance
/// @dev Returns an array of the current wave's leading IdeaIds where the array length is determined
/// by the protocol's number of available proposer delegates, fetched from the WaveCore contract
function getWinningIdeaIds() external view returns (uint256 minRequiredVotes, uint256 numEligibleProposers, uint96[] memory winningIds);
To view information about an IdeaId
/// @dev Returns the IdeaInfo struct associated with a given `ideaId`
function getIdeaInfo(uint256 ideaId) external view returns (IdeaInfo memory);
To view information about a Sponsorship
/// @dev Returns the SponsorshipParams struct associated with a given sponsor `address` and `ideaId`
function getSponsorshipInfo(address sponsor, uint256 ideaId) external view returns (SponsorshipParams memory);
To view all ideas which have won previous auctions and have already been proposed
/// @dev Returns IDs of ideas which have already won waves and been proposed to Nouns governance
/// @notice Intended for external use for improved devX
function getOrderedProposedIdeaIds() external view returns (uint96[] memory orderedProposedIds);
To fetch the next ideaId
or ERC1155 nonfungible tokenId
/// @dev Returns the next ideaId which makes use of the tokenId mechanic from the ERC1155 standard
function getNextIdeaId() external view returns (uint256);
To view the finalized yield which is available for claiming by a Nounder address
/// @dev Returns the funds available to claim for a Nounder who has delegated to Wave
function getClaimableYield(address nounder) external view returns (uint256);
To view the expected yield that will become available for claim after Wave finalization, determined by current onchain state of Wave Protocol and Nouns delegations
/// @dev Returns an estimate of expected yield for the given Nounder LP who has delegated voting power to Wave
/// @notice Returned estimate is based on optimistic state and is subject to change until Wave finalization
function getOptimisticYieldEstimate(address nounder) external view returns (uint256 yieldEstimate);
To fetch start and end block information about a given Wave
/// @dev Returns information pertaining to the given Wave ID as a WaveInfo struct
function getWaveInfo(uint256 waveId) external view returns (WaveInfo memory);
To fetch start and end block information about the current ongoing Wave
/// @dev Returns information pertaining to the current Wave as a WaveInfo struct
function getCurrentWaveInfo() external view returns (uint256 currentWaveId, WaveInfo memory currentWaveInfo);
To fetch the "parent" Wave in which a given ideaId
was created
/// @dev Returns the waveId representing the parent Wave during which the given ideaId was created
function getParentWaveId(uint256 ideaId) external view returns (uint256 waveId);
Protocol Usage and Development
Write Functions: Interacting with the protocol
To best understand the mechanisms of Wave Protocol's write functions, please refer directly to the NatSpec documentation within the external-facing contract interfaces.
These include the IdeaTokenHub Interface Contract as well as the Wave Core Interface Contract.
View Functions: Reading and querying the protocol
The Wave protocol core contract provides numerous convenience functions to improve offchain devX by returning values relevant for developing offchain components.
To view the current minimum votes required to submit an onchain proposal to Nouns governance
function getCurrentMinRequiredVotes() external view returns (uint256 minRequiredVotes);
To fetch the delegateId
for a given delegate address
function getDelegateId(address delegate) external view returns (uint256 delegateId);
To fetch a suitable Wave delegate for a given user based on their Nouns token voting power. This is the address the tokenholder should delegate to, using the Nouns token contract delegate()
function.
/// @dev Returns a suitable delegate address for an account based on its voting power
function getSuitableDelegateFor(address nounder)
external
view
returns (address delegate, uint256 minRequiredVotes);
To search for an available delegate of a given type:
/// @dev Returns either an existing delegate ID if one meets the given parameters, otherwise returns the next delegate ID
/// @param isSupplementary Whether or not to search for a Delegate that doesn't meet the current proposal threshold
/// @param minRequiredVotes Minimum votes to make a proposal. Must be more than current proposal threshold which is based on Nouns token supply
/// @return delegateId The ID of a delegate that matches the given criteria
function getDelegateIdByType(uint256 minRequiredVotes, bool isSupplementary)
external
view
returns (uint256 delegateId);
To view all existing "partial" Delegates, ie ones with voting power below the minimum required to make a proposal
function getAllPartialDelegates()
external
view
returns (uint256 minRequiredVotes, address[] memory partialDelegates);
To get the number of currently expected winning ideas- ie the number of Delegates that are currently eligible to propose
/// @dev Returns the number of existing Delegates currently eligible to make a proposal
function numEligibleProposerDelegates()
external
view
returns (uint256 minRequiredVotes, uint256 numEligibleProposers);
To view all existing Delegates that currently possess enough delegated voting power to push a Nouns proposal
/// @dev Returns all existing Delegates currently eligible for making a proposal
function getAllEligibleProposerDelegates()
external
view
returns (uint256 minRequiredVotes, uint256[] memory eligibleProposerIds);
The IdeaTokenHub likewise provides several convenience functions, some of which are listed below:
To view all existing IdeaIds that are eligible for proposal sorted by funding
/// @param optLimiter An optional limiter used to define the number of desired `ideaIds`, for example the number of
/// eligible proposers or winning ids. If provided, it will be used to define the length of the returned array
function getOrderedEligibleIdeaIds(uint256 optLimiter) external view returns (uint96[] memory orderedEligibleIds);
To view the leading IdeaIds which are expected to win this wave and be proposed to Nouns governance
/// @dev Returns an array of the current wave's leading IdeaIds where the array length is determined
/// by the protocol's number of available proposer delegates, fetched from the WaveCore contract
function getWinningIdeaIds() external view returns (uint256 minRequiredVotes, uint256 numEligibleProposers, uint96[] memory winningIds);
To view information about an IdeaId
/// @dev Returns the IdeaInfo struct associated with a given `ideaId`
function getIdeaInfo(uint256 ideaId) external view returns (IdeaInfo memory);
To view information about a Sponsorship
/// @dev Returns the SponsorshipParams struct associated with a given sponsor `address` and `ideaId`
function getSponsorshipInfo(address sponsor, uint256 ideaId) external view returns (SponsorshipParams memory);
To view all ideas which have won previous auctions and have already been proposed
/// @dev Returns IDs of ideas which have already won waves and been proposed to Nouns governance
/// @notice Intended for external use for improved devX
function getOrderedProposedIdeaIds() external view returns (uint96[] memory orderedProposedIds);
To fetch the next ideaId
or ERC1155 nonfungible tokenId
/// @dev Returns the next ideaId which makes use of the tokenId mechanic from the ERC1155 standard
function getNextIdeaId() external view returns (uint256);
To view the finalized yield which is available for claiming by a Nounder address
/// @dev Returns the funds available to claim for a Nounder who has delegated to Wave
function getClaimableYield(address nounder) external view returns (uint256);
To view the expected yield that will become available for claim after Wave finalization, determined by current onchain state of Wave Protocol and Nouns delegations
/// @dev Returns an estimate of expected yield for the given Nounder LP who has delegated voting power to Wave
/// @notice Returned estimate is based on optimistic state and is subject to change until Wave finalization
function getOptimisticYieldEstimate(address nounder) external view returns (uint256 yieldEstimate);
To fetch start and end block information about a given Wave
/// @dev Returns information pertaining to the given Wave ID as a WaveInfo struct
function getWaveInfo(uint256 waveId) external view returns (WaveInfo memory);
To fetch start and end block information about the current ongoing Wave
/// @dev Returns information pertaining to the current Wave as a WaveInfo struct
function getCurrentWaveInfo() external view returns (uint256 currentWaveId, WaveInfo memory currentWaveInfo);
To fetch the "parent" Wave in which a given ideaId
was created
/// @dev Returns the waveId representing the parent Wave during which the given ideaId was created
function getParentWaveId(uint256 ideaId) external view returns (uint256 waveId);
Voting while delegated to Wave Protocol
Because Wave Protocol is noncustodial and registers delegations using optimistic state, Nouns NFT holders who have delegated their voting power to Wave Protocol retain the right to vote and participate in the Nouns governance ecosystem at all times.
Important note: voting on Nouns proposals uses a checkpointing system, restricting votes to the voting power of each account when the proposal goes live! In order to vote you must be on top of the proposal's lifecycle and complete step 1 and 2 during the proposal's "Pending" state prior to the proposal going live for public voting.
To vote while delegated to Wave
Voting and participating while delegated to Wave comprises four major steps, three of which require sending an onchain transaction:
- Write down your Wave delegate's address as you will need it in the third step when redelegating to Wave. The address may be fetched from the Wave Core contract or Nouns Token contract.
- Delegate to yourself to reclaim your voting power. This undelegates from Wave Protocol
- Vote on the proposal(s) you're interested in, provided you met the time constraints noted above
- Redelegate to your Wave delegate (which you wrote down) before the completion of the current Wave
That's it! Profit :)
1. Fetching your Wave delegate's address
In order for Wave Protocol to guarantee your expected yield, you should record your delegate address and redelegate to the same delegate after voting. This can be done in multiple ways:
If you know your Delegate's address, write it down and move to step 2.
If you know your Delegate's ID but not its address, call Wave::getDelegateAddress(<delegateID>)
on the Wave core contract.
If you don't know your Delegate's address or ID, check what delegate address you are currently delegated to by calling NounsToken::delegates(<yourAddress>)
on the Nouns token contract.
Be sure to record your Wave delegate address so that you can redelegate to it after voting.
While this step is technically not necessary due to Wave Protocol's matchmaking mechanism, onchain state may change during the interim while you are delegated to yourself and voting. In certain cases this can lead to the protocol UI suggesting a different delegate ID for you when redelegating. In such a case, if your voting power is below the current minimum required votes to push a proposal (ie your voting power is considered a "partial" delegation in need of matchmaking), your yield is no longer guaranteed and may be dependent on the protocol finding another match for your liquidity.
2. Delegate to yourself
It is recommended to use the Nouns governance UI to redelegate your voting power to yourself (and away from Wave Protocol). Should the UI be unavailable for some reason, this can also be done at the contract level using NounsToken::delegate(<yourAddress>)
3. Vote on proposals
It is likewise recommended to use the Nouns governance UI to vote on the proposals you feel strongly about. Again, should the UI be unavailable for some reason, this can also be done at the contract level using NounsToken::castVote()
or some variation thereof (refundable, with reason, etc)
4. Redelegating to Wave Protocol
Simply redelegate to the Delegate address that you wrote down (and had been delegated to previously before voting). This must be done on the Nouns Token contract either using the Nouns governance UI or with NounsToken::delegate(delegateAddress)
Since your original delegation to Wave included registering an optimistic delegation in the Wave core contract's storage, there is no need to re-register or further interact with Wave Protocol contracts.
Aligning incentives to democratize access to Nouns governance, transmogrify Nouns NFTs into productive yield-bearing assets, and harness wider builder mindshare
Introducing Wave Protocol, an onchain crowdfunding system that extends the onchain Nouns Governance ecosystem by improving access to Nouns proposal creation and galvanizes community contributions by removing the barrier of entry to the Nouns sphere.
By Markus Osterlund, Protocol Lead @Wave
Table of Contents
Why extend Nouns governance?
Even with the correction in NFT market prices over recent years, the upfront capital cost of many NFTs still remains out of reach for many onchain participants. One such example is the Nouns governance token, which at time of writing boasts a $24,500 market price per NFT. Further, submitting an onchain proposal for consideration by the Nouns DAO requires two Nouns token votes, doubling the barrier of entry to a sizable $49,000.
What if ideas for Nouns proposals could come from anyone regardless of their tokenholdings and be judged on their merit, competing for selection before even hitting the canonical Nouns governance contracts as an official proposal? What if Nouns tokenholders could noncustodially earn yield from the competition and sponsorship of pre-proposal ideas? Builder mindshare would be extended beyond the ideations of those with expensive tokens, resulting in higher quality Nouns proposals and turning the Nouns NFT to a productive asset capable of generating yield.
Enter Wave Protocol, formerly known as PropLot.
Solving existing problems in the Nouns sphere
Wave Protocol solves three core issues facing the Nouns ecosystem:
- Getting a Nouns idea onchain to the voting stage is challenging
- Nouns is capital constrained and needs to invent new ways to fund itself aside from the daily auction
- Newcomers to Nouns do not have clear contributor highways
Removing the barrier of entry for community contributors
Without owning two Nouns NFTs outright in order to push a proposal, getting an idea in front of the Nouns DAO for voting is difficult. You'll either need to know people who will delegate their votes to you or you'll need to put up a candidate proposal and simply hope that the public carries it to the finish line.
We believe there is a better way to get your idea onchain than playing social & political games. As things stand, Nouns has a contributor highway problem. If you have an idea for contribution and want to get it funded, it’s not clear what steps to take. By protocolizing the process for getting an idea on-chain, we aim to provide a clear, meritocratic path for pre-proposal ideas which will result in more contributor ideas as well as more excitement for Nouns governance.
Innovating with new funding mechanisms for Nouns tokenholders and for the DAO
At current spend trajectories, Nouns DAO's capitalization amounts to anywhere from 8-14 months of runway before the treasury is completely dry. Like it or not, the DAO needs more ways of funding its treasury that are independent of the daily issuance auction. Further, the treasury owns 560 idle Nouns NFTs whose voting power is not utilized.
Noncustodially allocating a handful of idle treasury Nouns to Wave Protocol would monetize their voting power and fund the treasury in a novel way via delegation, without requiring the tokens to change hands.
Providing a public arena for community members who wish to contribute, enabling a public track record of builder provenance for scouting purposes.
For peripheral contributors, it is difficult to secure a delegate or even to be taken seriously on the governance forum and other public platforms like Twitter and Warpcast. Currently, those without voting power can participate by “supporting” a proposal and leaving a VWR but it can sometimes feel futile to do so since there is no guarantee that your efforts gain traction, no refund available for support actions, and no recognition for consistent support without existing builder provenance & reputation.
Wave protocol offers a new avenue for addressing these issues — the scout model.
In the traditional Venture Capital world, scouts help VCs with deal flow by sourcing startups and sending them to partners at the firm. In a similar vein, “nouns scouts” can use Wave Protocol to mint (ie sponsor) ideas that they believe should make it to the voting stage. Since sponsors receive an ERC1155 token as a receipt for supporting ideas, scouts build soulbound reputation over time, showcasing their ability to source contributions, ideas, and talented builders. In short, Wave introduces a new way of building soulbound social capital in the Nouns ecosystem.
Understanding Wave Protocol
For those interested in a Wave Protocol deep dive, consult our documentation
At its heart, Wave Protocol offers "Nouns governance proposals as a service" to peripheral community builders, termed "Idea Creators", who may not be capitalized enough to afford the 2 Nouns NFTs required to push their proposal onchain. The result is democratization of access to the Nouns sphere by lowering the barrier of entry for anyone with a worthy idea and desire to contribute.
Wave Protocol accepts Nouns token voting power noncustodially via delegation (more on this below), leveraging optimistic state to compensate registered Noun delegators with yield in exchange for delegating their voting power. The yield comprises the total funds raised by each Wave's winning ideas, which are represented as ERC1155 tokens.
Idea tokens that amass the highest capital from Sponsors are selected as winners at the conclusion of each Wave, the crowdfunding period during which ideas can be created and sponsored. The Wave Core contract determines the number of winning ideas per Wave and validates optimistic state at finalization based on its available "liquidity" (ie voting power) which it uses to push onchain proposals to the Nouns Governor.
In short, Wave Protocol opens a new participation layer that protocolizes the evaluation of ideas' merit before they are passed up to the upper echelon of the Nouns institution for voting.
The Nouns token's delegation ledger
Wave makes use of idle Nouns token voting power to engender a competitive idea machine powered by the untapped market of non-tokenholder mindshare. This is made possible by novel monetization of the Nouns NFT's second onchain ledger: the delegation ledger.
As opposed to the standard ownership ledger, which tabulates token balances reflecting which address owns which token, Nouns voting power is tabulated by a separate delegation ledger which is generally less well-understood.
The delegation ledger manages onchain state of addresses' voting power without affecting the top-level ownership of the tokens themselves. Voting power is transferred between addresses entirely without needing tokens to change hands simply by delegating and undelegating from desired representatives. This is the ledger that the Nouns NFT relies on to distinguish which addresses are eligible to push an onchain proposal to Nouns governance for voting.
For Nouns tokenholders: "LPs"
Are you looking to earn yield on your Nouns NFTs without giving up custody of your tokens? Rest assured you retain voting rights and can still participate in Nouns governance as usual.
Get started by delegating and registering your tokens' voting power to Wave Protocol using the UI!
More information can be found in the Protocol Participants Overview section of the Wave documentation
For Nouns builders: "Idea Creators"
Do you have an idea for a Nouns proposal and would like for your contribution to be voted on by the DAO in order to secure funding?
Get started by minting an ERC1155 idea token to enter it into the current Wave and compete for sponsorships!
More information can be found in the Protocol Participants Overview section of the Wave documentation
For Nouns community contributors: "Idea Sponsors"
Do you wish to financially support a pre-proposal idea and by extension its creator?
Get started with lobbying for ideas to become a proposal by jumping directly into the app and sponsoring worthy ideas!
More information can be found in the Protocol Participants Overview section of the Wave documentation
Live Deployments
Wave protocol is currently deployed in Beta on Base Sepolia testnet for backend & frontend development and finalized Ethereum mainnet deployments are coming soon.
Name | Contract Details | Contract Address |
---|---|---|
IdeaTokenHub | Harness, Proxy | 0xAFFED3815a60aACeACDA3aE53425f053eD6Efc4d |
Wave | Harness, Proxy | 0x443f1F80fBB72Fa40cA70A93a0139852b0563961 |
WaveRenderer | Singleton | 0xDAFF26c0C67B5a62077342D8487876Ed75Ad7f4A |
FontRegistry | Singleton | 0x765EeF8b5dD7af8FC7Aa03C76aFFd23AbcE7a3Bb |
PolymathFont | Singleton | 0xe2e6e42bf7Be8332c21652e05D385dAEDE4e9456 |
NounsToken | Harness | 0xE8b46D16107e1d562B62B5aA8d4bF9A60e6c51b4 |
NounsDescriptor | Singleton | 0x6cd473673A73150C8ff9Edc160262EBac3C882c0 |
NounsRenderer | Singleton | 0x09A80D276a4dBb6a400aF1c8663ed0cC2073cFE7 |
Note that the above testnet contracts deployed to Base Sepolia network are harnesses to expose convenience functions that would normally otherwise be protected to expedite development.