Parcel DAO Multisig Payments Integration

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

Developing Nouns-style DAO (auction-based)

Nouns DAO launched in August 2021 and changed how people think about NFTs and DAOs together. The mechanic is simple: one Noun (NFT) is auctioned every 24 hours, all proceeds go to the DAO treasury, each Noun = one vote in governance. No pre-mine, no VC allocation, no whitelist — just an open auction every day forever. As of writing, the treasury exceeds 30,000 ETH, and the system has been running continuously for over three years.

This is not just mechanics, it's a design pattern that gets reproduced: Lil Nouns, Gnars, Purple (Farcaster DAO), Builder DAO (Zora). Developing a Nouns-style DAO is a well-researched task with an open reference implementation.

Key System Components

A Nouns-style DAO consists of four smart contracts:

  1. NounsToken (ERC-721 + ERC20Votes) — NFT, each one is a voting unit
  2. NounsAuctionHouse — daily auction mechanism
  3. NounsGovernor — governance with fork mechanism (rage quit)
  4. NounsTreasury (Timelock) — treasury under governance control

Additionally: NounsDescriptor — on-chain SVG artwork generation, NounsSeeder — Chainlink VRF for random seed during generation.

NounsToken: NFT as voting unit

contract NounsToken is ERC721Checkpointable, INounsToken {
    // Address for Nounder's reward (every 10th Noun goes to founders)
    address public noundersDAO;
    
    // Only AuctionHouse can mint
    address public minter;
    
    // Seed for artwork generation
    mapping(uint256 => INounsSeeder.Seed) public seeds;
    INounsSeeder public seeder;
    INounsDescriptor public descriptor;
    
    uint256 private _currentNounId;
    
    function mint() public override onlyMinter returns (uint256) {
        // Every 10th Noun goes to founders (nounders reward)
        if (_currentNounId <= 1820 && _currentNounId % 10 == 0) {
            _mintTo(noundersDAO, _currentNounId++);
        }
        return _mintTo(minter, _currentNounId++);
    }
    
    function _mintTo(address to, uint256 nounId) internal returns (uint256) {
        // Get random seed from Seeder (Chainlink VRF or block hash)
        INounsSeeder.Seed memory seed = seeds[nounId] = seeder.generateSeed(nounId, descriptor);
        _mint(owner(), to, nounId);
        emit NounCreated(nounId, seed);
        return nounId;
    }
    
    // tokenURI is generated on-chain — no IPFS
    function tokenURI(uint256 tokenId) public view override returns (string memory) {
        require(_exists(tokenId), "URI query for nonexistent token");
        return descriptor.tokenURI(tokenId, seeds[tokenId]);
    }
    
    // dataURI — SVG directly in base64
    function dataURI(uint256 tokenId) public view override returns (string memory) {
        return descriptor.dataURI(tokenId, seeds[tokenId]);
    }
}

ERC-721 with checkpoint voting (ERC721Checkpointable)

Regular ERC-721 doesn't have voting power mechanics. Nouns extends it through a checkpoint system — analogous to ERC20Votes, but for NFTs:

abstract contract ERC721Checkpointable is ERC721Enumerable {
    mapping(address => address) private _delegates;
    
    struct Checkpoint {
        uint32 fromBlock;
        uint96 votes;
    }
    
    mapping(address => mapping(uint32 => Checkpoint)) public checkpoints;
    mapping(address => uint32) public numCheckpoints;
    
    function votesToDelegate(address delegator) public view returns (uint96) {
        return safe96(balanceOf(delegator), "ERC721Checkpointable: votes exceed 96 bits");
    }
    
    function delegate(address delegatee) public {
        return _delegate(msg.sender, delegatee);
    }
    
    function getPriorVotes(address account, uint256 blockNumber) public view returns (uint96) {
        require(blockNumber < block.number, "ERC721Checkpointable: not yet determined");
        
        uint32 nCheckpoints = numCheckpoints[account];
        if (nCheckpoints == 0) return 0;
        
        // Binary search through checkpoints
        if (checkpoints[account][nCheckpoints - 1].fromBlock <= blockNumber) {
            return checkpoints[account][nCheckpoints - 1].votes;
        }
        // ...binary search implementation
    }
}

NounsAuctionHouse: auction mechanics

This is the heart of the system. Every 24 hours — a new auction, a new Noun.

contract NounsAuctionHouse is INounsAuctionHouse, PausableUpgradeable, ReentrancyGuardUpgradeable, OwnableUpgradeable {
    INounsToken public nouns;
    address public weth;
    
    uint256 public timeBuffer;      // minimum time until auction end after bid (15 min)
    uint256 public reservePrice;    // minimum bid
    uint8 public minBidIncrementPercentage;  // minimum bid increment (%)
    uint256 public duration;        // auction duration (24 hours)
    
    INounsAuctionHouse.Auction public auction;
    
    struct Auction {
        uint256 nounId;
        uint256 amount;       // current bid
        uint256 startTime;
        uint256 endTime;
        address payable bidder;
        bool settled;
    }
    
    function createBid(uint256 nounId) external payable override nonReentrant {
        INounsAuctionHouse.Auction memory _auction = auction;
        
        require(_auction.nounId == nounId, "Noun not up for auction");
        require(block.timestamp < _auction.endTime, "Auction expired");
        require(msg.value >= reservePrice, "Must send at least reservePrice");
        require(
            msg.value >= _auction.amount + ((_auction.amount * minBidIncrementPercentage) / 100),
            "Must send more than last bid by minBidIncrementPercentage amount"
        );
        
        address payable lastBidder = _auction.bidder;
        
        // Return to previous bidder
        if (lastBidder != address(0)) {
            _safeTransferETHWithFallback(lastBidder, _auction.amount);
        }
        
        auction.amount = msg.value;
        auction.bidder = payable(msg.sender);
        
        // If bid came within timeBuffer of end — extend
        bool extended = _auction.endTime - block.timestamp < timeBuffer;
        if (extended) {
            auction.endTime = block.timestamp + timeBuffer;
        }
        
        emit AuctionBid(_auction.nounId, msg.sender, msg.value, extended);
        
        if (extended) {
            emit AuctionExtended(_auction.nounId, auction.endTime);
        }
    }
    
    function settleCurrentAndCreateNewAuction() external override nonReentrant whenNotPaused {
        _settleAuction();
        _createAuction();
    }
    
    function _settleAuction() internal {
        INounsAuctionHouse.Auction memory _auction = auction;
        
        require(_auction.startTime != 0, "Auction hasn't begun");
        require(!_auction.settled, "Auction has already been settled");
        require(block.timestamp >= _auction.endTime, "Auction hasn't completed");
        
        auction.settled = true;
        
        if (_auction.bidder == address(0)) {
            // No one bid — Noun goes to treasury
            nouns.transferFrom(address(this), owner(), _auction.nounId);
        } else {
            nouns.transferFrom(address(this), _auction.bidder, _auction.nounId);
        }
        
        if (_auction.amount > 0) {
            // ETH goes to treasury (Timelock)
            _safeTransferETHWithFallback(owner(), _auction.amount);
        }
        
        emit AuctionSettled(_auction.nounId, _auction.bidder, _auction.amount);
    }
    
    // Fallback: if ETH transfer fails — send WETH
    function _safeTransferETHWithFallback(address to, uint256 amount) internal {
        if (!_safeTransferETH(to, amount)) {
            IWETH(weth).deposit{ value: amount }();
            IERC20(weth).transfer(to, amount);
        }
    }
}

Anti-sniping: time buffer

timeBuffer is critical protection against last-second sniping. If a bid comes within 15 minutes of auction end — the end shifts another 15 minutes. This effectively removes the incentive to bid in the last seconds — the auction will continue as long as people want to bid.

On-chain SVG artwork: NounsDescriptor

One of the most innovative parts of Nouns — artwork is entirely stored on-chain. No IPFS, no centralized server. A Noun is generated from a set of layers (backgrounds, bodies, accessories, heads, glasses) stored as RLE-compressed data right in the contract.

contract NounsDescriptor {
    // Compressed artwork layers in bytes (RLE encoding)
    bytes[] public bodies;
    bytes[] public accessories;
    bytes[] public heads;
    bytes[] public glasses;
    
    // Generate SVG from seed
    function generateSVGImage(INounsSeeder.Seed memory seed) 
        external view returns (string memory svg) 
    {
        ISVGRenderer.SVGParams memory params = ISVGRenderer.SVGParams({
            parts: _getPartsForSeed(seed),
            background: backgrounds[seed.background]
        });
        return renderer.generateSVG(params);
    }
    
    function tokenURI(uint256 tokenId, INounsSeeder.Seed memory seed)
        external view override returns (string memory)
    {
        string memory name = string(abi.encodePacked('Noun ', tokenId.toString()));
        string memory description = string(abi.encodePacked('Noun ', tokenId.toString(), ' is a member of the Nouns DAO'));
        
        return genericDataURI(name, description, seed);
    }
    
    function genericDataURI(
        string memory name,
        string memory description,
        INounsSeeder.Seed memory seed
    ) public view override returns (string memory) {
        NFTDescriptor.TokenURIParams memory params = NFTDescriptor.TokenURIParams({
            name: name,
            description: description,
            parts: _getPartsForSeed(seed),
            background: backgrounds[seed.background]
        });
        return NFTDescriptor.constructTokenURI(renderer, params);
    }
}

Completely on-chain NFT means permanence. Nouns will exist as long as Ethereum exists.

NounsGovernor: extended Governor with fork mechanism

Nouns Governor differs from standard OpenZeppelin Governor through two key mechanics: objection period and fork.

Objection period

After voting ends — an additional period (48 hours) during which only Against votes are accepted. If a proposal passed the vote but in the final moment many votes against appeared — last-minute For votes won't help pass the proposal. This protects against last-minute vote manipulation.

enum ProposalState {
    Pending,
    Active,
    Canceled,
    Defeated,
    Succeeded,
    Queued,
    Expired,
    Executed,
    Vetoed,
    ObjectionPeriod  // New state
}

function state(uint256 proposalId) public view override returns (ProposalState) {
    // ...standard logic...
    
    // Check if objection period is needed
    if (isForVotesSucceeded && !isObjectionPeriodOver) {
        // If significant Against votes came in recent blocks
        if (proposal.objectionPeriodEndBlock > block.number) {
            return ProposalState.ObjectionPeriod;
        }
    }
}

Fork mechanism (rage quit at proposal level)

The most original part of Nouns v3: if a major holder disagrees with an accepted proposal, they can initiate a fork. The DAO splits: dissenters take their proportional treasury share to a new fork DAO.

function escrowToFork(
    uint256[] calldata tokenIds,
    uint256[] calldata proposalIds,
    string calldata reason
) external onlyNounOwner {
    // Tokens are escrowed — can't be used for voting while in escrow
    for (uint256 i = 0; i < tokenIds.length; i++) {
        nouns.safeTransferFrom(msg.sender, address(forkEscrow), tokenIds[i]);
    }
    
    emit NounsEscrowed(msg.sender, tokenIds, proposalIds, reason);
    
    // If escrow token count exceeds forkThreshold
    // (e.g. 20% of total supply) — fork can be activated
    if (_isForkThresholdReached()) {
        _activateFork();
    }
}

function _activateFork() internal {
    // New fork DAO is created with same parameters
    // Treasury is split proportionally by tokens in escrow
    uint256 forkedTreasuryAmount = (address(timelock).balance * escrowedTokens) / totalSupply;
    
    // Deploy new NounsToken and NounsGovernor for fork DAO
    (address forkToken, address forkTreasury) = forkDAODeployer.deployForkDAO(
        forkEscrow.numTokensInEscrow(),
        forkedTreasuryAmount,
        block.timestamp + FORK_PERIOD  // period when others can join fork
    );
    
    // Transfer ETH to fork treasury
    payable(forkTreasury).transfer(forkedTreasuryAmount);
}

This is a fundamentally different approach to governance minority protection compared to standard rage quit in Moloch-style DAOs.

Customization for a specific project

Builder DAO (Zora) created a factory for launching Nouns-style DAOs without coding: TokenFactory deploys custom contracts with specified parameters. Gnars (skateboarding), Purple (Farcaster), Federation — all use Builder DAO framework.

Customization parameters:

Parameter Nouns Typical fork Builder DAO default
Auction duration 24 hours 12–48 hours 24 hours
Reserve price 1 ETH 0.01–1 ETH 0
Founder allocation 10% (every 10th) 5–15% Configurable
Voting delay 1 day 1 hour – 2 days 1 day
Voting period 3 days 2–7 days 3 days
Quorum 10% 5–20% 10%

On-chain artwork vs IPFS

Completely on-chain artwork is expensive deployment (storing bytes in EVM costs gas). Nouns spent significant sums deploying descriptor with artwork. For most forks, the optimal compromise: artwork on IPFS with on-chain hash, Descriptor generates metadata dynamically using IPFS CID.

Economics: model sustainability

Nouns-style model creates a flywheel: auction → ETH in treasury → proposals → DAO activity → attention → higher bid on next auction. This is a self-financing system without external fundraising.

The key sustainability question: how long does participation stay high. Nouns solves this through constant on-chain activity (daily auction is an event), community quality (each Noun is expensive, so owners are engaged), and fork mechanism (minority protection prevents participants from "exit by dumping").

Development stack for a fork

Component Reference Alternative
Auction contract Nouns AuctionHouse Builder DAO factory
NFT + voting ERC721Checkpointable ERC721Votes (OZ)
Artwork storage NounsDescriptor (on-chain) IPFS + descriptor
Random seed Chainlink VRF v2 Prevrandao (simpler, less reliable)
Governor NounsGovernor v3 OZ Governor
Frontend nouns.wtf open source Builder DAO UI

Development stages

Phase Content Timeline
Parameter design Auction duration, pricing, founder allocation, governance 1–2 weeks
NFT contract + artwork ERC-721 + checkpoint voting + descriptor 3–4 weeks
Auction house Bid mechanics, settlement, anti-snipe 2–3 weeks
Governor + Timelock Governance with custom parameters 2–3 weeks
Artwork preparation Creating/preparing layers, encoding 2–4 weeks (depends on art)
Tests Full coverage, fork simulation, governance attack scenarios 2–3 weeks
Frontend Auction UI, governance, NFT gallery 4–6 weeks
Audit 3–4 weeks

The first auction launch is a public event: it needs community attention before deployment. The mechanic is transparent and understandable even without technical background — competitive advantage over complex DeFi protocols.