Table of Contents
This project translates Alex Tabarrok’s “dominant assurance” contracts idea to the blockchain in the form of a working escrow contract for the crowdfunding of a property. Click here for more info on dominant assurance. This alternative mechanism can be extended to fund any public good.
Using "DAOntown" as the main campaign example, any pledger that pledges to the campaign will be able to mint and claim DAOntown tokens, which could represent their share of governance rights, land use rights, etc. The token contract is presently written with the ERC20 standard, but could easily be replaced with the ERC721 or other token standard if desired.
createCampaign()
is called by a campaign creator to populate the campaign struct for all users. Each campaign is given a unique campaign ID. The campaign struct is organized as follows:struct Campaign { string title; address creator; uint256 targetAmount; uint256 refundBonus; uint256 campaignExpiryDate; uint32 maxEarlyPledgers; address[] pledgers; address[] earlyPedgers; uint256 totalPledgedAmount; bool isGoalMet; bool hasCompletedRefunds; bool creatorHasWithdrawnFunds; }
pledge()
will make a pledge to the campaign associated with the campaign ID that the user enters. Pledge sends funds to the crowdfunding platform contract.withdrawRefund()
is only callable after a campaign expires that has not met its goal. This function can only be called by pledgers of the specific unsuccessful campaign.creatorWithdrawal()
can only be called by the creator of the specific campaign ID entered as an argument. This function can only be called upon successful campaign. Creator can choose any address to withdraw all the campaign funds to.- A bunch of getters:
getCampaignInfo()
,getCampaignFundingStatus()
,getCampaignCount()
,getBalance()
,getCampaignEarlyPledgers()
,getCampaignPledgers()
,getAmountPledged()
,getAddress()
,getCampaignRefundsCompleted()
,getCampaignAmountRefunded()
,getEarlyRefundCalc()
claimDAOntownTokens()
is really the only function that can be called on this contract by campaign pledgers. In this project example, DAOntown tokens are minted and sent 1-for-1 to pledgers based on how much ETH they pledged to the campaign. These tokens can be used as governance tokens, land use right tokens, etc.mint()
can only be called by the token contract owner. Ownership could be transferred to a multisig after a successful campaign so that the property token holders can vote on if they want to mint more tokens or keep with the existing supply.
Crowdfund w/ Dominant Assurance (DomCrowdfund.sol) Contract Address: 0x81fa7a47bE7BBa84a6391773e8481725310563C8
Crowdfund Contract Page - Optimism-Goerli Testnet
DAOntown Token (DAOntownToken.sol) Contract Address: 0xAB9098d0C2F056f7b3BfacDFc8A67ceb4CF185f8
DAOntown Token Contract Page - Optimism-Goerli Testnet
Test Coverage Report:
-
Testnet ETH is needed to interact with the app. It can be obtained from this faucet, then bridged to Optimism Goerli.
-
Please use MetaMask to interact with the app.
-
Most everything is tied to the campaign ID#. Users need it when calling functions for pledging, withdrawing, and claiming tokens.
-
Though the smart contracts are working properly on chain, the front end has some visual bugs. To interact with a full life cycle of a campaign, after you have created the campaign, do not refresh the page until you are done interacting with that campaign. Refreshes will wipe the slate clean, though you can still find the campaign you just created or an older one using the Find Campaign element. If the front end gets extra bug-y for some reason, as a reminder, the crowdfunding smart contract is working as intended, so you can always run through complete life cycles using the Block Explorer Read and Write Contract pages, since the contract is verified.
Here's a couple of screenshots of the app in action:
For quickstart, clone the repo using the link from Github, cd
into directory, and run npm install
.
Then cd
into the /app
directory and run npm install
.
To launch the front-end application, run npm start
from the /app
directory. Open http://localhost:3000 to view it in your browser.
- Smart contracts: Solidity, Hardhat (deploy, toolbox, chai matchers, mocha, network helpers), Ethers, prettier, solhint
- OpenZeppelin inherited contracts: Ownable, ReentrancyGuard
- Front End: React, Webpack, Craco, Babel, Ethers, detectEthereumProvider
- Add edge case or bizarre case testing in unit tests and staging tests.
- Add user action feedback, e.g. creating visual displays when a campaign has met a goal from a pledge, error messaging, etc.
- Add more functionality on the front end to get more precise info for existing or past campaigns.
- Add more flexibility in the user inputs for payment amounts and campaign lengths.
- I have to remind myself that bug locations are often not in obvious places. If a particular element is not working properly, there's a good chance it is because of another element elsewhere. Slowing down and backing up can help bring more causes and effects into focus.
Scott Auriat was the main consultant and sounding board for this project.
Distributed under the MIT License.
Armand Daigle - @_Starmand - armanddaoist@gmail.com
Thanks to Scott Auriat for his consultation on different aspects, as well as introducing me to the dominant assurance strategy.
A big thanks to the fellow Alchemy University students and community for helping me learn a lot about React.