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:

NameContract DetailsContract Address
IdeaTokenHubProxy0x000000000088b111eA8679dD42f7D55512fD6bE8
WaveProxy0x00000000008DDB753b2dfD31e7127f4094CE5630
WaveRendererSingleton0x65DBB4C59d4D5d279beec6dfdb169D986c55962C
PolymathFontSingleton0xf3A20995C9dD0F2d8e0DDAa738320F2C8871BD2b
NounsTokenDependency0x9C8fF314C9Bc7F6e59A9d9225Fb22946427eDC03
NounsGovernorDependency0x6f3E6272A167e8AcCb32072d08E0957F9c79223d

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

Git Source

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

NameTypeDescription
optLimiteruint256An 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

Git Source

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

NameTypeDescription
optLimiteruint256An 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

Git Source

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

NameTypeDescription
minRequiredVotesuint256Minimum votes to make a proposal. Must be more than current proposal threshold which is based on Nouns token supply
isSupplementaryboolWhether or not to search for a Delegate that doesn't meet the current proposal threshold

Returns

NameTypeDescription
delegateIduint256The 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

NameTypeDescription
_minRequiredVotesuint256The votes needed to make a proposal, dynamic based on Nouns token supply
_isSupplementaryboolWhether 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

Git Source

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

Git Source

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

Git Source

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

NameTypeDescription
optLimiteruint256An 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

Git Source

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

NameTypeDescription
minRequiredVotesuint256Minimum votes to make a proposal. Must be more than current proposal threshold which is based on Nouns token supply
isSupplementaryboolWhether or not to search for a Delegate that doesn't meet the current proposal threshold

Returns

NameTypeDescription
delegateIduint256The 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

NameTypeDescription
delegatoraddressOnly token holder addresses are stored since Delegates can be derived
blockDelegateduint32Block at which a Noun was delegated, used for payout calculation. Only records delegations performed via this contract, ie not direct delegations on Nouns token
votingPoweruint16Voting 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)
delegateIduint16

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:

  1. Getting a Nouns idea onchain to the voting stage is challenging
  2. Nouns is capital constrained and needs to invent new ways to fund itself aside from the daily auction
  3. 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.

NameContract DetailsContract Address
IdeaTokenHubHarness, Proxy0xAFFED3815a60aACeACDA3aE53425f053eD6Efc4d
WaveHarness, Proxy0x443f1F80fBB72Fa40cA70A93a0139852b0563961
WaveRendererSingleton0xDAFF26c0C67B5a62077342D8487876Ed75Ad7f4A
FontRegistrySingleton0x765EeF8b5dD7af8FC7Aa03C76aFFd23AbcE7a3Bb
PolymathFontSingleton0xe2e6e42bf7Be8332c21652e05D385dAEDE4e9456
NounsTokenHarness0xE8b46D16107e1d562B62B5aA8d4bF9A60e6c51b4
NounsDescriptorSingleton0x6cd473673A73150C8ff9Edc160262EBac3C882c0
NounsRendererSingleton0x09A80D276a4dBb6a400aF1c8663ed0cC2073cFE7

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.