/ tutorials / dao-getting-started.md
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*