Skip to content

dutch-protocol/Protocol-Contracts

Repository files navigation

DUTCH PROTOCOL

alt text

Project Description

DUTCH PROTOCOL is an NFT auction platform featuring integrated deflationary tokenomics. The system implements a bonding curve-backed token economy designed to create sustainable value accrual through auction fee monetization and systematic token supply reduction.

Quick Overview

  • DUTCH token: ERC‑20 with a walled‑garden design – transfers are restricted/allow‑listed so all price discovery flows through the bonding‑curve engine.
  • BondingCurve: Standalone contract implementing the bonding‑curve math for WETH/DUTCH, with symmetric buy/sell taxes that fund the protocol. Public buyDutchExactIn, buyDutchExactOut, and redeemDUTCH.
  • DutchVault + Marketplace: Vault receives the NPA tax share, accumulates ETH by collection, buys floor NFTs, and lists them on the Dutch auction marketplace with configurable pricing.
  • Auction Assist: Let users temporarily co‑fund vault purchases; when NFTs are sold, they receive pro‑rata DUTCH rewards and the protocol burns the rest.
  • Presale: Optional ETH presale that mints DUTCH via the same curve, then streams tokens to early participants using Sablier V2.

Table of Contents


Core Components

1. $DUTCH Token (ERC-20)

The $DUTCH token serves as the platform's exclusive currency with a controlled trading environment. As an ERC-20 token with 18 decimals, it maintains deflationary properties from launch without venture capital dilution or vesting schedules (unless bought in the public presale).

Component Summary:

  • Controlled transfers: Only allow‑listed protocol contracts can freely move tokens when restrictions are enabled.
  • Curve‑driven supply: Minting/burning is owned by the bonding‑curve engine; users cannot mint directly.
  • User burn: Anyone can send DUTCH to 0xdead via userBurn without changing the curve’s effective supply, increasing backing per circulating token.

Walled Garden Approach
The contract employs transfer restrictions and blacklist functionality to prevent external DEX trading, ensuring all transactions flow through the bonding curve mechanism. While total blacklisting of all DEXes is not feasible, targeting major exchanges (Uniswap, SushiSwap, etc.) should be sufficient to maintain the walled garden approach and ensure the protocol captures the majority of tax revenue from trading activities.

Supply Management
Supply control is exclusively managed by the bonding-curve engine (BondingCurve) through dual supply tracking:

  • totalSupply() - circulating tokens
  • bondingCurveSupply() - pricing calculations

In addition, an allowlist of protocol addresses (_allowedTransferors) can always transfer even when global restrictions are turned on, enabling controlled integrations (e.g., vaults, marketplaces, or presale contracts).

User Burn Mechanism
The userBurn() function transfers tokens to burn address (0xdead) without affecting either supply metric, creating treasury surplus and unreachable ETH reserves that strengthen protocol backing.

Administrative Controls:

Function Purpose Access
pause()/unpause() Emergency halt of all token operations Owner only
setTransfersRestricted() Toggle global P2P transfer restrictions Owner only
setBlacklisted() Add/remove addresses from blacklist Owner only
setAllowedTransferor(address,bool) Add/remove protocol addresses from the transfer allowlist Owner only
transferOwnership()/acceptOwnership() Two-step ownership transfer Owner/Pending Owner

Key Security Features:

  • OpenZeppelin Pausable for emergency stops
  • Ownable2Step prevents accidental ownership loss
  • Blacklist functionality for compliance
  • Transfer restrictions to maintain walled garden

Configuration Options:

Setting Function Parameters Limits Default
Transfer Restrictions setTransfersRestricted(bool) true/false - false
Blacklist Management setBlacklisted(address, bool) wallet address, status - None blacklisted
Emergency Pause pause()/unpause() - - Unpaused

2. BondingCurve (Bonding Curve Engine)

Single on-chain component (BondingCurve.sol) that implements the bonding curve economics for WETH/DUTCH. It is a standalone contract (no Uniswap or other AMM).

Component Summary:

  • Single source of truth for price and supply: all DUTCH minting/burning goes through this contract.
  • Symmetric taxes on buys/sells fund DutchVault (NPA) and the ops wallet.
  • Slippage protection on public buyDutchExactIn, buyDutchExactOut, and redeemDUTCH.

Core Function: Manages all DUTCH minting/burning with algorithmic price stability and routes buy/sell taxes to protocol destinations.

Key Features:

Feature Details
Pricing Model price = C × supply^1.5 (C = 500,000,000 wei)
Reserve Asset WETH/ETH-only backing with internal balance tracking
Tax Structure 10% buy tax, 10% sell tax (symmetric design)
Tax Distribution 75% → NPA wallet, 25% → Operations wallet
Security ReentrancyGuard + Pausable

Entry Points:

Function Caller Purpose
buyDutchExactIn(minOut, recipient) Anyone Exact-in: ETH → DUTCH (buy tax unless exempt)
buyDutchExactOut(exactDutch, recipient) Anyone Exact-out: DUTCH for ETH (buy tax unless exempt)
redeemDUTCH(amount, minEth) Anyone Sell DUTCH for ETH (sell tax applied)

Design Purpose:

  • Provide on-chain price discovery for DUTCH via the polynomial curve.
  • Generate protocol revenue through symmetric buy/sell taxes.
  • Fund NFT acquisition via DutchVault and protocol operations via the ops wallet.

Core Operations Flow (High Level):

  • Minting path (buys): User or presale/marketplace sends ETH; contract computes tax, routes NPA/ops shares, deposits net WETH into reserve, and mints DUTCH per the integral price = C × supply^1.5.
  • Redemption path (sells): User calls redeemDUTCH; contract burns DUTCH, applies sell tax, and sends net ETH to the user.

Treasury & Actors (Conceptual):

Actor Role Description
User/Investor Token Operations Buys or sells $DUTCH via buyDutchExactIn / buyDutchExactOut / redeemDUTCH
Protocol Owner Governance Updates fees, tax splits, and destination wallets
Contract Reserve Treasury Internal WETH/ETH balance backing redemptions (not a wallet)
NPA Wallet NFT Purchase Account Receives 75% of taxes; accumulates ETH to buy floor-priced NFTs
Ops Wallet Operations Team-owned wallet receiving 25% of taxes for marketing purposes

Fund Flow Summary:

Source Allocation Destination Purpose
Mint ETH 90% after tax Contract Reserve WETH backing for redemptions
Buy Tax (10%) 75% / 25% NPA / Operations NFT purchases / Team operations
Sell Tax (10%) 75% / 25% NPA / Operations NFT purchases / Team operations
Excess Reserves 100% NPA Wallet Additional NFT acquisition funding

Observability:

  • getBondingCurveSupply() – effective curve supply (totalMinted - totalBurned).
  • getReserveBalance() – WETH reserve backing the curve.
  • getTotalMinted() / getTotalBurned() – cumulative DUTCH minted and burned.

3. DutchVault (NFT Purchase Account)

Smart-contract treasury that receives the NPA (vault) share of bonding‑curve taxes and automates NFT purchasing, listing, and settlement accounting.

Component Summary:

  • Allocates ETH across collections using configurable basis‑point weights.

  • Permissionless buy/list helpers so anyone can trigger NFT purchases and listings within configured risk bounds.

  • Tracks realized PnL and burned DUTCH for full transparency of vault performance.

  • Per‑collection allocation engine:

    • Owner configures collections and their weights via setCollectionAllocations(...) (must sum to 10000 BPS).
    • Incoming ETH from the bonding curve is split across collections on receive() according to these weights, with round‑robin dust handling.
    • Per‑collection balances and base prices are tracked and exposed via view helpers (e.g., getCollectionBalance, getBasePrice, getCollections, getActiveCollections).
  • Automated buying & price caps:

    • Anyone can call buyNFTForCollection(...) to purchase a floor NFT for a configured collection using that collection’s ETH balance.
    • DutchVault enforces:
      • Sufficient allocated balance and non‑zero base price.
      • Function selector allowlisting via setAllowedMarketplaceSelector(marketplace, selector, true) for the purchase function used when buying NFTs.
      • A dynamic max price cap per collection (getMaxPriceForCollection) based on basePrice + blocksSinceLastBuy × buyIncrement.
    • Successful purchases create on‑chain inventory records (InventoryRecord) and update metrics like _totalNFTsPurchased and _totalCostETH.
  • Listing & settlement flow:

    • Anyone can:
      • listNFTOnMarketplace(...) for held NFTs, which computes min/max auction prices from configurable multipliers (setAuctionMultipliers).
      • buyAndListNFT(...) in a single transaction.
    • Listings are created on DutchAuctionMarketplace either as:
      • Standard listings (proceeds returned to the vault), or
      • Burn listings (all DUTCH proceeds burned) when there are no AuctionAssist contributions.
    • Settlements can be:
      • Recorded manually via recordSettlement(...) (owner‑only), or
      • Automatically via onListingSettled(...) callback from the marketplace.
    • Settlement logic (_processSettlement) updates:
      • Inventory flags (listed/realized, items held/sold),
      • Total DUTCH burned (getTotalDUTCHBurned),
      • Realized profit/loss (getTotalRealizedProfit, getTotalRealizedLoss),
      • And then calls _splitProceeds(...) to share DUTCH between AuctionAssist contributors and protocol burns.
  • AuctionAssist integration:

    • setAuctionAssist(address) wires in the AuctionAssist contract (or disables it with address(0)).
    • receiveContribution(...) lets AuctionAssist push ETH into a specific collection balance and updates _totalETHReceived.
    • On purchases, if AuctionAssist is configured, DutchVault notifies it via IAuctionAssist.recordPurchase(...), and on settlement it shares DUTCH proceeds pro‑rata with contributors while burning the protocol’s share via DUTCHToken.userBurn.
  • Admin & safety:

    • Owner can pause/unpause all external flows (pause, unpause) and withdraw stranded per‑collection ETH via withdrawCollectionBalance(...) (with balance and zero‑withdrawal safeguards).
    • All NFT‑moving and settlement paths are nonReentrant, and marketplace interactions are guarded with explicit checks and custom errors (e.g., MarketplaceCallFailed, NFTNotAcquired, NFTAlreadyOwned).

4. NFT Auction Marketplace

Marketplace infrastructure for NFT listings and planned deflationary auction mechanisms.

Component Summary:

  • Dutch auctions with configurable max/min prices and durations.
  • Protocol listings with burn to route proceeds directly into DUTCH burns when there are no external contributors.
  • Configurable listing fees and whitelisting to control which collections can trade and how fees are split.

Core Functions:

Component Function
Listing Creation createListing() - Users list NFTs with max/min ETH prices
Protocol Listings createListingWithBurn() - Owner creates listings with burn recipient
Listing Management cancelListing() - Seller or current owner can cancel
Settlement settle() - Purchase with DUTCH tokens, or settleWithETH() - Purchase with ETH (protocol listings only)
Collection Whitelist Optional filtering of allowed NFT collections
Listing Fee (createListing) 0.5% of min price (min 0.1 ETH), split 75/25 to NPA/Ops (default, configurable)

Fee Structure:

  • Listing Fee: max(minPrice × 0.5%, 0.1 ETH) paid in ETH when creating listing
  • Listing Fee Distribution: 75% to Dutch Vault (NPA), 25% to Protocol Operations (default, configurable)
  • Settlement Fee: 2.5% of sale price (configurable)
  • Settlement Fee Distribution: 100% burned for third-party listings (default, configurable split between Vault/Ops/Burn)
  • Auction Duration: 12 hours (configurable default)

Configuration Options:

Setting Function Parameters Limits Default
Collection Whitelist setCollectionWhitelistEnabled(bool) true/false - false
Collection Access setCollectionAllowed(address, bool) Collection address, allowed - All allowed (if whitelist disabled)
Emergency Pause pause()/unpause() - - Unpaused

Note: Settlement fees (2.5%), listing fees (0.5%), listing fee minimum (0.1 ETH), auction duration (12 hours), and all fee split ratios are configurable.


5. Auction Assist

The auction assist system lets users deposit ETH to top up DutchVault’s per‑collection balances and receive pro‑rata DUTCH rewards when those NFTs are eventually sold.

Component Summary:

  • ETH in → DUTCH out: Users contribute ETH toward specific collections and later receive a share of DUTCH proceeds when NFTs sell.

  • Bounded contributor sets per collection to keep gas predictable.

  • Automatic burning of any non‑allocated DUTCH, reinforcing deflationary tokenomics.

  • Contribution flow:

    • Users call contribute(collection) with ETH.
    • AuctionAssist:
      • Validates the collection has a non‑zero base price in DutchVault (getBasePrice), otherwise reverts CollectionNotConfigured.
      • Computes a contribution percentage in BPS relative to that base price and records it in a per‑user ContributionRecord (amount, percentageBPS).
      • Tracks contributors per collection (capped at MAX_CONTRIBUTORS = 100 to keep gas bounded) and which collections each user has positions in.
      • Forwards the ETH directly to DutchVault via receiveContribution(collection, contributor).
  • Purchase snapshotting (DutchVault → AuctionAssist):

    • When DutchVault buys an NFT for a collection, it calls recordPurchase(collection, tokenId, costETH):
      • Only DutchVault is authorized (UnauthorizedCaller otherwise).
      • A new purchaseId is created, and an NFTPurchaseInfo is stored (collection, tokenId, cost, total contributed ETH, list of contributors).
      • For each contributor on that collection, AuctionAssist snapshots their share in BPS (_purchaseShares[purchaseId][contributor]) relative to the actual cost (capped at 10000 BPS).
      • Emits PurchaseRecorded(purchaseId, collection, tokenId, costETH, contributorCount).
  • Reward distribution & burns:

    • On settlement, DutchVault calls recordAndPullRewards(purchaseId, dutchAmount) on AuctionAssist (where dutchAmount is the DUTCH proceeds share for that purchase). AuctionAssist validates the purchase, marks rewards as funded, and pulls DUTCH from DutchVault; the protocol share is later burned via burnProtocolShare(purchaseId) once all contributors have claimed.
    • Contributors call claimReward(purchaseId) to receive their pro‑rata DUTCH. Each claim is limited to the contributor's share; RewardsAlreadyClaimed prevents double claims. Unclaimed protocol share is burned via burnProtocolShare(purchaseId) (callable by anyone once all contributors have claimed).
  • Views & UX helpers:

    • getContribution(user, collection) – raw contribution record (amount + percentageBPS).
    • getTotalContributions(collection) – total ETH contributed for a collection.
    • getPurchaseInfo(purchaseId) / getPurchaseShare(purchaseId, contributor) – inspection of per‑purchase snapshots.
    • getCollectionContributors(collection) – list of contributors for a collection.
    • getUserCollections(user) and getUserActivePositions(user) – front‑end friendly views of all positions a user has across collections.
  • Admin & safety:

    • Owner can pause / unpause all contributions and reward distributions.
    • Uses ReentrancyGuard and SafeERC20 for safe external calls and token transfers.
    • Strong input validation and custom errors prevent misconfiguration (e.g., zero addresses, zero contribution amounts, invalid purchase IDs).

6. Presale (Sablier V2 Token Streaming)

Optional presale module that collects ETH during a fixed window, converts it to DUTCH via the bonding‑curve engine, and creates Sablier V2 vesting streams for early participants.

Component Summary:

  • Time‑boxed ETH raise with a hard cap and minimum ticket size.

  • Single atomic finalization that mints via the curve and sets up all vesting streams.

  • On‑chain vesting analytics and optional claim helper on top of Sablier’s own UI.

  • Configuration & lifecycle:

    • Immutable parameters set at deployment:
      • presaleStart – timestamp when deposits open.
      • presaleCap – maximum ETH that can be raised.
      • PRESALE_DURATION – duration of the sale (7 days).
      • VESTING_CLIFF / VESTING_DURATION – 6‑month cliff + 12‑month linear vest.
    • Owner must later wire dependencies via:
      • setContracts(dutchToken, bondingCurve) – connects IDUTCHToken and BondingCurve.
      • setSablierV2Address(address) – sets the Sablier Lockup Linear contract (before vesting starts).
  • Deposit phase:

    • Users call depositEth():
      • Enforces min contribution (MIN_CONTRIBUTION), time window (presaleStartpresaleStart + PRESALE_DURATION), and global cap (presaleCap).
      • Tracks per‑user contributions and maintains a contributors array.
      • Emits EthDeposited(user, amount).
    • View helpers:
      • checkFundedAmount() – total totalFundedAmount.
      • getUserContribution(user) – each user’s ETH.
      • getContributorCount() – number of unique contributors.
  • Upkeep & finalization:

    • checkUpkeep(...) (Chainlink‑style) reports when the sale is ready to finalize:
      • Requires token, BondingCurve, and Sablier addresses to be set.
      • Presale must be ended, totalFundedAmount > 0, and vestingInitiated == false.
    • performUpkeep(...) can be called by anyone once checkUpkeep is true:
      • Internally:
        • Calls _mintTokens() which sends all ETH to BondingCurve.buyDutchExactIn() (BondingCurve handles tax distribution internally).
        • Stores minted token amounts and approves Sablier for stream creation.
        • Marks vestingInitiated = true and sets vestingStartTime.
        • Emits PresaleFinalized(totalEth, totalTokens, contributorCount).
      • After performUpkeep(), individual contributors must call claimVestingStream(user) to create their vesting stream.
    • finalizePresale() is a convenience wrapper around performUpkeep().
  • Bonding‑curve quote helpers:

    • getPresaleQuote(ethAmount) – view helper that:
      • Before vesting: calls bondingCurve.getQuoteWETHtoDUTCH(ethAmount) to get expected tokens, tax, and net ETH.
      • After vesting: returns stored values from totalVestingTokensDetails (dutchAmount, taxAmount, ethUsed).
    • getUserAllocation(user) – returns expected token allocation for a user’s ETH contribution given the final totalFundedAmount.
  • Sablier V2 vesting:

    • _createVestingStreams():
      • Reads the full DUTCH balance of the presale contract after minting and approves Sablier.
      • For each contributor:
        • Computes userTokenAllocation = totalTokens * userContribution / totalFundedAmount.
        • Builds LockupLinear durations and unlock amounts (0 at start, 100% at cliff).
        • Calls sablierV2.createWithDurationsLL(...) and stores the streamId in vestingStreamIds[user].
        • On success, emits TokensClaimed(user, allocation) as a “stream created with this amount” signal.
        • On failure, emits StreamCreationFailed(user, allocation) but continues with other contributors.
      • Emits VestingStreamsCreated(participantCount) with the number of successful streams.
  • User vesting views & claiming:

    • getUserVestingInfo(user) – returns stream ID, total receivable, unlocked, claimed, and full schedule (start, cliff, end), plus a progress BPS (0–10000).
    • canUserClaim(user) – helper that checks cliff time and returns (canClaim, timeUntilCliff).
    • getUserStreamId(user) – direct access to vestingStreamIds[user] for off‑chain Sablier interactions.
    • claimVestedTokens(amount) – optional convenience:
      • Uses Sablier’s withdrawMax when amount == 0 or withdraw for a specified amount.
      • Emits TokensClaimed(user, withdrawnAmount) for app‑level tracking.

Prerequisites/Requirements


Project Structure

└── src/
    ├── DUTCHToken.sol
    ├── BondingCurve.sol
    ├── DutchAuctionMarketplace.sol
    ├── DutchVault.sol
    ├── AuctionAssist.sol
    ├── Presale.sol
    └── interfaces/
        ├── IDUTCHToken.sol
        ├── IDutchVault.sol
        ├── IDutchAuctionMarketplace.sol
        └── IAuctionAssist.sol

Contract Inheritance & Dependencies

Contract Inherits From Interfaces Used Math Libraries
DUTCHToken ERC20, Ownable2Step, Pausable - -
BondingCurve Ownable2Step, ReentrancyGuard, Pausable IDUTCHToken, IWETH9 UD60x18 (PRB Math)
DutchAuctionMarketplace Ownable2Step, Pausable, ReentrancyGuard IDUTCHToken, IDutchVault, IDutchAuctionMarketplace, IERC721 SafeERC20
DutchVault Ownable2Step, Pausable, ReentrancyGuard, IERC721Receiver IDUTCHToken, IDutchAuctionMarketplace, IAuctionAssist, IERC721 SafeERC20
AuctionAssist Ownable2Step, Pausable, ReentrancyGuard IDutchVault, IDUTCHToken, IAuctionAssist SafeERC20
Presale Ownable2Step, ReentrancyGuard, Pausable IDUTCHToken, BondingCurve, ISablierLockup UD60x18 (PRB Math)
IDUTCHToken Interface - -

Library Usage Details

Library Purpose Used In
UD60x18 (PRB Math) Fixed-point arithmetic for bonding curve calculations BondingCurve.sol, Presale.sol
SafeERC20 (OpenZeppelin) Safe ERC-20 transfers and allowance management DutchAuctionMarketplace.sol, DutchVault.sol, AuctionAssist.sol

Interfaces Used

Interface Purpose Used In
IERC721 (OpenZeppelin) NFT interface for marketplace operations DutchAuctionMarketplace.sol, DutchVault.sol
IDUTCHToken (Custom) Token interface for bonding curve and protocol interactions DUTCHToken.sol, BondingCurve.sol, DutchAuctionMarketplace.sol, DutchVault.sol, AuctionAssist.sol, Presale.sol
IDutchVault (Custom) Interface for the DutchVault treasury DutchVault.sol, DutchAuctionMarketplace.sol, AuctionAssist.sol
IDutchAuctionMarketplace (Custom) Typed access to the Dutch auction marketplace DutchAuctionMarketplace.sol, DutchVault.sol
IAuctionAssist (Custom) Interface for the Auction Assist module DutchVault.sol, AuctionAssist.sol
ISablierLockup (Sablier V2) Token streaming and lockup primitives for presale vesting Presale.sol
IWETH9 (Uniswap v4-periphery) Wrapped ETH interface used by BondingCurve BondingCurve.sol

Dependencies

Dependencies are managed with Soldeer for secure, versioned package management.

Name Version Commit/Tag GitHub
OpenZeppelin Contracts v5.5.0 fcbae53 https://github.com/OpenZeppelin/openzeppelin-contracts
PRB Math v4.1.0 280fc5f https://github.com/PaulRBerg/prb-math
Forge Standard Library v1.14.0 - https://github.com/foundry-rs/forge-std
Solmate v1.0.0 89365b8 https://github.com/transmissions11/solmate
Uniswap v4 Periphery v1.0.3 3779387 https://github.com/Uniswap/v4-periphery
Sablier Lockup v2.0.1 baf9a9e https://github.com/sablier-labs/lockup

Installation/Setup Instructions

  1. Clone the repository:
git clone [repository-url]
cd Protocol-Contracts
  1. Install Foundry (if not already installed):
curl -L https://foundry.paradigm.xyz | bash
foundryup
  1. Install dependencies:

See BUILD_GUIDE.md for detailed build instructions and dependency management with Soldeer.

  1. Build the project:
forge build

Testing Commands

Run the test suite:

forge test

License Information

This project is licensed under the BSL-1.1 (Business Source License 1.1).

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors