Gradual Dutch Auctions on Penumbra

Penumbra’s DEX engine has powerful tools to allow liquidity providers to precisely control the prices they offer on-chain. This works well when prices are already known – but what should users do when prices are not known, and need to be discovered?

To address this use case, Penumbra has protocol-level support for Dutch auctions, which allow smooth and gradual price discovery for new assets with unknown prices, as well as better execution and reduced price impact for swaps of existing assets.

In this post, we’ll describe what Dutch auctions are, how Dutch auctions are supported on Penumbra, and the benefits this protocol feature unlocks for users.

Dutch Auctions

Dutch auctions are a simple and time-tested auction mechanism, based on descending prices: the auction begins at a high price, and this price decreases over time. As soon as a bidder indicates they are willing to buy at the current price, the bidder wins the auction and the auction ends.

Dutch auctions offer a number of attractive properties: They behave similarly to a sealed-bid auction, as each participants’ bids are kept secret until the winner submits their bid. They are also “shill-proof”, a particularly important property for permissionless systems like blockchains – because the auction ends at the first bid, the auctioneer cannot submit fake “shill bids” to inflate the price. Finally, they are also simple and easy to implement.

One drawback of traditional Dutch Auctions, however, is that they obtain a single point-in-time price as a result of the auction. When the price of an asset is unknown, it may be desirable to spread price discovery over time, similar to a TWAP (time-weighted average price). To address this, gradual Dutch auctions perform multiple distinct Dutch auctions over a longer time period, averaging the results across each auction.

Lazy Swaps

One capability unlocked by on-chain auctions is Lazy Swaps, using auctions spaced over time to fulfill a user’s trade intent. Typically, swap transactions attempt to exchange asset X for Y in one instantaneous trade against the liquidity available in that moment. Sometimes, this is what a user wants, but more commonly a user’s intent is less time-sensitive: “trade X for Y within 5 minutes”, “trade X for Y within 1 hour”, or even “trade X for Y over the next two days”. In this case, the user’s intent could be better served by performing Penumbra’s version of a Dutch Auction.

Swap UI

Instead of consuming liquidity from the DEX, a Gradual Dutch Auction allows users to provide liquidity over time, and potentially be matched against more time-sensitive traders. By spreading a trade over time, fluctuations in price impact are reduced on the user who is expressing a less time-sensitive trade intent.

This feature is especially useful for assets with low liquidity or those that are new to the market. In such cases, establishing a fair market value can be challenging due to limited trading history or market data. However, but using a Gradual Dutch Auction, users can collectively discover the fair market value: sellers can do a long-duration lazy swap from X to Y, buyers can do a long-duration lazy swap from Y to X, and both sides of the market will have their liquidity gradually matched at the market-clearing price, without requiring active intervention. While they both do so, either side provides passive liquidity to the network.

As a result, the structured price decline and extended bidding period provide a clearer picture of how the market values the asset, benefiting both buyers and sellers as an asset finds a mutually established price across auctions.

Deep Dive: How Dutch Auctions Function on Penumbra

Penumbra has protocol-level support for Dutch auctions via a custom auction component. Currently, the auction component only supports Dutch auctions, but more auction types could be added in the future. Let’s look at how auctions work at the protocol level.

To initiate an auction, a user submits a transaction with an ActionDutchAuctionSchedule action. Penumbra transactions are bundles of “actions” which contribute or withdraw from the transaction’s value balance and perform on-chain actions. The ActionDutchAuctionSchedule action consumes the input asset the user wishes to sell and produces an “auction NFT” representing ownership of the newly created auction. This specifies the source and destination assets as well as the starting height and price and the ending height and price of the auction, causing the chain to schedule the auction to begin at the user-specified start height.

Because auctions are scheduled in advance, users can plan a sequence of individual auctions all in one transaction, without having to remain online for the duration of the auction. Auctions are also guaranteed to execute once they are scheduled, adding censorship-resistance - a user can post their GDA. The auctions themselves are public, allowing everyone to see what auctions have been scheduled, but the auction NFT itself is recorded privately in the shielded pool like any other asset. The identity of the auctioneer as a result remains private.

At the beginning of each block, the auction component’s BeginBlock handler looks for any scheduled auctions that need to be handled. Auctions are implemented using the DEX: the auction object manages a concentrated liquidity position on the DEX offering tokens at the current price. This position can be matched against like any other DEX liquidity, so there is no special bid mechanism. Instead, swaps can execute against an auction-controlled LP, or be cleared via on-chain arbitrage if the price declines relative to other liquidity positions and crosses the spread.

To end an auction, the user submits a transaction with an ActionDutchAuctionEnd action. This action consumes their existing auction NFT and produces a new auction NFT with an incremented sequence number. If the auction is still open, it is closed, allowing users to cancel active auctions; if the auction has been completed, this action is a no-op and only advances the state of the user’s auction NFT.

Finally, to withdraw a completed auction, a user submits a transaction with an AuctionDutchAuctionWithdraw, consuming their (closed) auction NFT and releasing the auction’s final reserves, in turn allowing the user to record the resulting funds in the shielded pool. Separating the “close” and “withdraw” actions ensures that the auction reserves are frozen when the user is withdrawing funds back to the shielded pool, but the actions can be stacked together in a single transaction if the auction was closed as completed by the chain.

Conclusion

Penumbra’s Gradual Dutch Auctions provide a powerful tool for accomplishing price discovery and efficiently fulfilling user intents, and are only one of a slew of features offered by a DEX from the future. By offering users flexibility over the way they buy and sell assets, the network also benefits from the feature’s use by allowing new or low-liquidity assets to find their price on the DEX’s market, all while preserving the privacy of those who are posting their trade intents as a default quality of the feature.

A full description of how Dutch auctions are implemented can be found in the design docs, and information on how to use Penumbra can be found in the Penumbra Guide.