dao-getting-started.md
1 # Getting Started with the #B4mad DAO 2 3 > **Bead:** beads-hub-e3a — "DAO Phase 1.5: Tutorial for DAO interaction" 4 5 This tutorial walks you through interacting with the **#B4mad DAO** as a contributor — from understanding the governance token to voting on proposals and checking the treasury. 6 7 ## Prerequisites 8 9 - **Docker** (for the containerized dev environment) 10 - **Node.js ≥ 18** (for ethers.js examples) 11 - **Foundry** (`cast` CLI) — [install guide](https://book.getfoundry.sh/getting-started/installation) 12 13 ### Dev Environment 14 15 All contract ABIs and deployment artifacts are available in our container image: 16 17 ```bash 18 docker pull ghcr.io/brenner-axiom/b4mad-dao-contracts:latest 19 20 # Run an interactive shell with all tools pre-installed 21 docker run -it --rm ghcr.io/brenner-axiom/b4mad-dao-contracts:latest bash 22 ``` 23 24 Inside the container you'll find: 25 - Compiled contract ABIs in `/app/artifacts/` 26 - Deployment addresses in `/app/deployments/` 27 - Pre-configured `foundry.toml` and helper scripts 28 29 ### Environment Variables 30 31 Set these before running the examples below: 32 33 ```bash 34 # RPC endpoint (use the network where the DAO is deployed) 35 export RPC_URL="https://rpc.gnosis.gateway.fm" 36 37 # Contract addresses (check /app/deployments/ in the container for current values) 38 export TOKEN_ADDRESS="0x..." # $B4MAD ERC-20 governance token 39 export GOVERNOR_ADDRESS="0x..." # Governor contract (OpenZeppelin Governor) 40 export TIMELOCK_ADDRESS="0x..." # TimelockController (treasury) 41 export YOUR_ADDRESS="0x..." # Your wallet address 42 export PRIVATE_KEY="0x..." # Your private key (NEVER commit this) 43 ``` 44 45 --- 46 47 ## 1. What Is the $B4MAD Governance Token? 48 49 The **$B4MAD** token is an [ERC-20](https://eips.ethereum.org/EIPS/eip-20) governance token with [ERC20Votes](https://docs.openzeppelin.com/contracts/4.x/api/token/erc20#ERC20Votes) extensions. It powers the #B4mad DAO: 50 51 | Property | Details | 52 |---|---| 53 | **Standard** | ERC-20 + ERC20Votes (OpenZeppelin) | 54 | **Voting weight** | 1 token = 1 vote (delegated) | 55 | **Delegation** | You **must delegate** before your tokens count as voting power — even to yourself | 56 | **Transferable** | Yes, but governance weight follows delegation | 57 58 ### Key Concept: Delegation 59 60 Holding tokens alone does **not** give you voting power. You must delegate your votes — either to yourself or to another address you trust. 61 62 #### ethers.js — Delegate to Yourself 63 64 ```js 65 import { ethers } from "ethers"; 66 67 const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); 68 const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider); 69 70 const tokenAbi = [ 71 "function delegate(address delegatee) external", 72 "function delegates(address account) external view returns (address)", 73 "function getVotes(address account) external view returns (uint256)", 74 ]; 75 const token = new ethers.Contract(process.env.TOKEN_ADDRESS, tokenAbi, signer); 76 77 // Delegate votes to yourself 78 const tx = await token.delegate(signer.address); 79 await tx.wait(); 80 console.log("Delegated to:", await token.delegates(signer.address)); 81 console.log("Voting power:", ethers.formatEther(await token.getVotes(signer.address))); 82 ``` 83 84 #### cast — Delegate to Yourself 85 86 ```bash 87 cast send $TOKEN_ADDRESS "delegate(address)" $YOUR_ADDRESS \ 88 --rpc-url $RPC_URL --private-key $PRIVATE_KEY 89 ``` 90 91 --- 92 93 ## 2. How to Receive Tokens (Contributor Allocation) 94 95 $B4MAD tokens are allocated to contributors by the DAO through governance proposals. The typical flow: 96 97 1. **A proposal is created** to mint/transfer tokens to a contributor's address 98 2. **Token holders vote** on the proposal 99 3. **After the voting period**, if quorum is met and the vote passes, the proposal is queued 100 4. **After the timelock delay**, anyone can execute the proposal, and tokens are transferred 101 102 ### Check Your Token Balance 103 104 #### ethers.js 105 106 ```js 107 const balanceAbi = ["function balanceOf(address) view returns (uint256)"]; 108 const token = new ethers.Contract(process.env.TOKEN_ADDRESS, balanceAbi, provider); 109 110 const balance = await token.balanceOf(process.env.YOUR_ADDRESS); 111 console.log("Balance:", ethers.formatEther(balance), "$B4MAD"); 112 ``` 113 114 #### cast 115 116 ```bash 117 cast call $TOKEN_ADDRESS "balanceOf(address)(uint256)" $YOUR_ADDRESS \ 118 --rpc-url $RPC_URL | cast from-wei 119 ``` 120 121 --- 122 123 ## 3. How to View Proposals 124 125 The DAO uses an [OpenZeppelin Governor](https://docs.openzeppelin.com/contracts/4.x/governance) contract. Each proposal has a unique `proposalId` (a uint256 hash). 126 127 ### Proposal States 128 129 | State | Meaning | 130 |---|---| 131 | 0 — Pending | Voting hasn't started yet | 132 | 1 — Active | Voting is open | 133 | 2 — Canceled | Proposal was canceled | 134 | 3 — Defeated | Vote failed (quorum not met or more against) | 135 | 4 — Succeeded | Vote passed, awaiting queue | 136 | 5 — Queued | In timelock, awaiting execution | 137 | 6 — Expired | Timelock expired without execution | 138 | 7 — Executed | Proposal was executed | 139 140 #### ethers.js — Check Proposal State 141 142 ```js 143 const governorAbi = [ 144 "function state(uint256 proposalId) view returns (uint8)", 145 "function proposalVotes(uint256 proposalId) view returns (uint256 againstVotes, uint256 forVotes, uint256 abstainVotes)", 146 "function proposalDeadline(uint256 proposalId) view returns (uint256)", 147 "function proposalSnapshot(uint256 proposalId) view returns (uint256)", 148 ]; 149 const governor = new ethers.Contract(process.env.GOVERNOR_ADDRESS, governorAbi, provider); 150 151 const proposalId = "123..."; // The proposal ID you want to check 152 153 const state = await governor.state(proposalId); 154 const stateNames = ["Pending", "Active", "Canceled", "Defeated", "Succeeded", "Queued", "Expired", "Executed"]; 155 console.log("State:", stateNames[state]); 156 157 const [against, forVotes, abstain] = await governor.proposalVotes(proposalId); 158 console.log(`Votes — For: ${ethers.formatEther(forVotes)}, Against: ${ethers.formatEther(against)}, Abstain: ${ethers.formatEther(abstain)}`); 159 ``` 160 161 #### cast — Check Proposal State 162 163 ```bash 164 # Get proposal state (returns a number 0-7) 165 cast call $GOVERNOR_ADDRESS "state(uint256)(uint8)" $PROPOSAL_ID \ 166 --rpc-url $RPC_URL 167 168 # Get vote tallies 169 cast call $GOVERNOR_ADDRESS \ 170 "proposalVotes(uint256)(uint256,uint256,uint256)" $PROPOSAL_ID \ 171 --rpc-url $RPC_URL 172 ``` 173 174 --- 175 176 ## 4. How to Vote on Proposals 177 178 You can vote **For** (1), **Against** (0), or **Abstain** (2) on any active proposal where you had delegated voting power at the proposal's snapshot block. 179 180 #### ethers.js — Cast a Vote 181 182 ```js 183 const governorAbi = [ 184 "function castVote(uint256 proposalId, uint8 support) external returns (uint256)", 185 "function castVoteWithReason(uint256 proposalId, uint8 support, string reason) external returns (uint256)", 186 "function hasVoted(uint256 proposalId, address account) view returns (bool)", 187 ]; 188 const governor = new ethers.Contract(process.env.GOVERNOR_ADDRESS, governorAbi, signer); 189 190 const proposalId = "123..."; 191 192 // Vote FOR (1) with a reason 193 const tx = await governor.castVoteWithReason(proposalId, 1, "Strong proposal, aligns with roadmap"); 194 const receipt = await tx.wait(); 195 console.log("Vote cast! Tx:", receipt.hash); 196 197 // Check if you've voted 198 const voted = await governor.hasVoted(proposalId, signer.address); 199 console.log("Has voted:", voted); 200 ``` 201 202 #### cast — Cast a Vote 203 204 ```bash 205 # Vote FOR (support=1) 206 cast send $GOVERNOR_ADDRESS \ 207 "castVoteWithReason(uint256,uint8,string)" \ 208 $PROPOSAL_ID 1 "Looks good to me" \ 209 --rpc-url $RPC_URL --private-key $PRIVATE_KEY 210 211 # Check if you already voted 212 cast call $GOVERNOR_ADDRESS \ 213 "hasVoted(uint256,address)(bool)" \ 214 $PROPOSAL_ID $YOUR_ADDRESS \ 215 --rpc-url $RPC_URL 216 ``` 217 218 --- 219 220 ## 5. How to Check Treasury Status 221 222 The DAO treasury is managed by the **TimelockController** contract. Any ETH or ERC-20 tokens held by the timelock address are DAO funds. 223 224 #### ethers.js — Check Treasury 225 226 ```js 227 // Check native token (ETH/xDAI) balance 228 const treasuryBalance = await provider.getBalance(process.env.TIMELOCK_ADDRESS); 229 console.log("Treasury native balance:", ethers.formatEther(treasuryBalance)); 230 231 // Check $B4MAD tokens held by treasury 232 const tokenAbi = ["function balanceOf(address) view returns (uint256)"]; 233 const token = new ethers.Contract(process.env.TOKEN_ADDRESS, tokenAbi, provider); 234 235 const tokenBalance = await token.balanceOf(process.env.TIMELOCK_ADDRESS); 236 console.log("Treasury $B4MAD balance:", ethers.formatEther(tokenBalance)); 237 ``` 238 239 #### cast — Check Treasury 240 241 ```bash 242 # Native balance (ETH/xDAI) 243 cast balance $TIMELOCK_ADDRESS --rpc-url $RPC_URL | cast from-wei 244 245 # $B4MAD token balance in treasury 246 cast call $TOKEN_ADDRESS "balanceOf(address)(uint256)" $TIMELOCK_ADDRESS \ 247 --rpc-url $RPC_URL | cast from-wei 248 ``` 249 250 --- 251 252 ## Quick Reference 253 254 | Action | cast one-liner | 255 |---|---| 256 | Check balance | `cast call $TOKEN_ADDRESS "balanceOf(address)(uint256)" $YOUR_ADDRESS --rpc-url $RPC_URL` | 257 | Delegate votes | `cast send $TOKEN_ADDRESS "delegate(address)" $YOUR_ADDRESS --rpc-url $RPC_URL --private-key $PRIVATE_KEY` | 258 | Check voting power | `cast call $TOKEN_ADDRESS "getVotes(address)(uint256)" $YOUR_ADDRESS --rpc-url $RPC_URL` | 259 | Proposal state | `cast call $GOVERNOR_ADDRESS "state(uint256)(uint8)" $PROPOSAL_ID --rpc-url $RPC_URL` | 260 | Vote FOR | `cast send $GOVERNOR_ADDRESS "castVote(uint256,uint8)" $PROPOSAL_ID 1 --rpc-url $RPC_URL --private-key $PRIVATE_KEY` | 261 | Treasury balance | `cast balance $TIMELOCK_ADDRESS --rpc-url $RPC_URL` | 262 263 --- 264 265 ## Next Steps 266 267 - **Explore the contracts** inside the dev container: `docker run -it ghcr.io/brenner-axiom/b4mad-dao-contracts:latest bash` 268 - **Read the OpenZeppelin Governor docs**: [docs.openzeppelin.com/contracts/4.x/governance](https://docs.openzeppelin.com/contracts/4.x/governance) 269 - **Join the #B4mad community** to discuss proposals before they go on-chain 270 271 --- 272 273 *Last updated: 2026-02-19 · Bead: beads-hub-e3a*