Gloom

How decentralized is DeFi? If you look at the ownership of governance tokens, the answer is "not very." With few exceptions, a handful of addresses hold majority stakes in most DeFi projects.
Let's say you're a founder or investor in a top project like Compound, Maker, Synthetix, Aave or Chainlink and you'd like to sell a large stake. The market caps for the tokens of these projects is in the billions, so even a 5% stake is worth hundreds of millions of USD.
How could you go about selling your tokens? There wouldn't be enough order book liquidity on a centralized exchange like Binance, nor enough pair liquidity on a decentralized AMM like Uniswap.
Large transaction today are typically negotiated off-chain through personal relationships, or in a centralized way through market makers. I think there's a better, decentralized way: a private auction – akin to an M&A sales process in traditional finance – conducted on the Ethereum blockchain.
This idea is the inspiration for Gloom, a platform I built at the ConsenSys Blockchain Developer bootcamp which enables a seller to hold an invite-only auction of ERC-20 tokens. Using Gloom, a seller invites bidders (contacts and/or "whales") to bid on the tokens, with the seller and winning bidder exchanging tokens and payment (in ETH) through an escrow contract.
Auction process
- Setup: seller configures the type and amount of tokens, makes an ETH deposit into the auction contract, invites bidders (Ethereum addresses) and sets a bidder ETH deposit requirement.
- Commit: bidders deposit into the auction contract and present their bids (in ETH) for the tokens. Bids are hidden (salted and hashed) and recorded on the blockchain.
- Reveal: bidders reveal their bids (only if they match the earlier commits), with the winner determined and an escrow contract deployed.
- Deliver: seller and winning bidder deliver tokens and payment, respectively, to the escrow contract.
- Withdraw: seller and winning bidder withdraw proceeds and tokens, respectively, from the escrow contract. Everyone withdraws their deposits from the auction contract.
Links
- GitHub (mono repo with a React project and Truffle project with Solidity smart contracts)
- Video demo walkthrough (12 minutes)
Minimal-proxy
As the first step in conducting an auction, the seller configures the type and amount of ERC-20 tokens they wish to sell. On submit, the React frontend (using ethers.js) calls a function on the auction factory which deploys a new auction using a minimal-proxy pattern, implemented using Open Zeppelin's ProxyFactory contract.
Minimal proxies are a way to clone contracts in order to save on transaction gas fees. When I first implemented the factory using a traditional, non-proxy pattern, creating an auction required more than 1 million gwei of gas vs. a little over 200k currently. Essentially, the minimal proxy reuses the bytecode of a pre-deployed "logic" contract using delegate calls, a pattern standardized in EIP-1167.
Commit-reveal
Since data on the Ethereum blockchain is publicly accessible, Gloom uses a commit-reveal pattern to conceal bids. In the commit phase, bid hashes are stored on the blockchain, hiding the amounts. Users supply passwords that are used as salts when hashing in order to prevent brute-force attacks.
In the reveal phase, bidders re-enter their bids and if the hashes match (which forces bidders to be honest), the bids are stored on the blockchain, with the winner declared. My code is based on Austin Griffith's commit-reveal implementation.
Escrow delivery
When the seller triggers the deliver phase, the auction contract declares the winner and deploys a new escrow contract. The seller and bidder then deposit their tokens and payment (in ETH), respectively, into the contract, with checks to ensure both have deposited before allowing withdrawals.
Escrow withdrawal
Once the deposits have been completed, the seller and winning bidder are able to withdraw their deposits from the auction contract, and their sale proceeds and tokens, respectively, from the escrow contract. For withdrawals, state updates are performed before transfers made in order to prevent reentrancy attacks, one example of the design patterns used in Gloom aimed at avoiding common attacks.
Tests
I wrote unit tests for the Solidity smart contracts in JavaScript using Truffle's test framework, which builds on top of Mocha and uses Chai for assertions. I added the Truffle Assertions package for testing expected revert conditions (e.g. unauthorized message sender).
Web3-react
On the React frontend, Gloom uses the web3-react package by Uniswap's Noah Zinsmeister to connect with the web3 provider that is injected into the browser (e.g. by MetaMask), putting it into a Context.
Hooks & ethers.js
A React webapp will typical fetch data from an API, but a dApp like Gloom pulls data from the blockchain using a library like ethers.js, which can connect to Ethereum nodes through an injected provider (e.g. MetaMask, which connects to a node through the Infura API). Effectively, the smart contracts serve here as the backend.
I also frequently used ethers.js to set up contract event listeners within Effect Hooks to watch for state changes on the blockchain and then update the UI once they fire.
Design
As the saying goes, "good artists copy, great artists steal." Gloom's neumorphism "soft UI" design is based on code I forked from Marta Mullor and CodingNepal.