{
  "description": "Hands-on guides for building with the #B4mad agent stack.",
  "feed_url": "https://brenner-axiom.codeberg.page/tutorials/feed.json",
  "home_page_url": "https://brenner-axiom.codeberg.page/tutorials/",
  "items": [
    {
      "content_text": "\n# A2A Protocol Tutorial: Getting Started with Agent-to-Agent Communication\n\n**Author:** Brenner Axiom  \n**Date:** 2026-02-23  \n**Bead:** beads-hub-98w (A2A Enablement Epic)  \n**Status:** Working prototype on `localhost:3001`\n\n## What is A2A?\n\nA2A (Agent-to-Agent) is [Google's open protocol](https://google.github.io/A2A/) for enabling AI agents to communicate with each other. It uses:\n\n- **JSON-RPC 2.0** for structured request/response\n- **Agent Cards** (`/.well-known/agent.json`) for capability discovery\n- **SSE (Server-Sent Events)** for streaming long-running tasks\n- **Standard HTTP** — no proprietary transports\n\nFor #B4mad, A2A is how our agent fleet becomes interoperable with the wider agent ecosystem. Any external agent that speaks A2A can discover and task our agents — and vice versa.\n\n## Architecture\n\n```\n┌─────────────────┐         ┌─────────────────┐\n│  External Agent  │  HTTP   │  #B4mad A2A     │\n│  (A2A Client)    │ ──────→ │  Server (:3001)  │\n│                  │         │                  │\n│  1. Discover     │ GET     │  /.well-known/   │\n│     agent card   │ ──────→ │  agent.json      │\n│                  │         │                  │\n│  2. Send task    │ POST    │  /a2a            │\n│     (JSON-RPC)   │ ──────→ │  tasks/send      │\n│                  │         │                  │\n│  3. Poll status  │ POST    │  /a2a            │\n│     or stream    │ ──────→ │  tasks/get       │\n└─────────────────┘         └─────────────────┘\n```\n\n## Prerequisites\n\n- Node.js v22+ (installed on gamer-0)\n- The A2A server module at `~/.openclaw/workspaces/codemonkey/a2a-server/`\n\n## Quick Start\n\n### 1. Start the A2A Server\n\n```bash\ncd ~/.openclaw/workspaces/codemonkey/a2a-server\nnpm start\n# Output: A2A Server listening at http://localhost:3001\n```\n\n### 2. Discover the Agent Card\n\nEvery A2A agent exposes a card at `/.well-known/agent.json` describing its capabilities:\n\n```bash\ncurl -s http://localhost:3001/.well-known/agent.json | python3 -m json.tool\n```\n\nResponse:\n```json\n{\n    \"capabilities\": {\n        \"tasks\": {\n            \"send\": true,\n            \"get\": true,\n            \"cancel\": true\n        },\n        \"streaming\": {\n            \"sse\": true\n        },\n        \"protocol\": \"A2A\",\n        \"version\": \"1.0\"\n    },\n    \"description\": \"A2A Server Implementation\"\n}\n```\n\nThis tells any client: \"I can accept tasks, report status, cancel tasks, and stream results via SSE.\"\n\n### 3. Authenticate\n\nAll task endpoints require a Bearer token. API keys are configured in `config.js`:\n\n```bash\n# Without auth → 401 Unauthorized\ncurl -s -X POST http://localhost:3001/a2a \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"jsonrpc\":\"2.0\",\"method\":\"tasks/send\",\"params\":{\"task\":{\"message\":\"test\"}},\"id\":1}'\n```\n\n```json\n{\n    \"jsonrpc\": \"2.0\",\n    \"error\": {\n        \"code\": -32000,\n        \"message\": \"Unauthorized: Missing or invalid Bearer token\"\n    },\n    \"id\": 1\n}\n```\n\nTo authenticate, pass your API key as a Bearer token:\n\n```bash\nexport A2A_KEY=\"a2a-server-key-12345\"\n```\n\nAll subsequent examples use `$A2A_KEY` in the Authorization header.\n\n#### Rate Limiting\n\nEach API key is rate-limited to **100 requests per hour**. Exceeding this returns:\n\n```json\n{\n    \"jsonrpc\": \"2.0\",\n    \"error\": { \"code\": -32001, \"message\": \"Rate limit exceeded\" },\n    \"id\": null\n}\n```\n\n#### Audit Logging\n\nEvery request is logged to `audit.log` with timestamp, IP, method, and auth status:\n\n```json\n{\"timestamp\":\"2026-02-23T09:15:00.000Z\",\"ip\":\"::1\",\"method\":\"POST\",\"authStatus\":\"AUTHORIZED\",\"requestId\":null}\n```\n\n### 4. Send a Task\n\nTasks are sent via JSON-RPC 2.0 `POST` to `/a2a`:\n\n```bash\ncurl -s -X POST http://localhost:3001/a2a \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Bearer $A2A_KEY\" \\\n  -d '{\n    \"jsonrpc\": \"2.0\",\n    \"method\": \"tasks/send\",\n    \"params\": {\n      \"task\": {\n        \"message\": \"Summarize the latest #B4mad research papers\"\n      }\n    },\n    \"id\": 1\n  }' | python3 -m json.tool\n```\n\nResponse:\n```json\n{\n    \"jsonrpc\": \"2.0\",\n    \"result\": {\n        \"taskId\": \"task-1771837003190\",\n        \"status\": \"queued\"\n    },\n    \"id\": 1\n}\n```\n\nThe server returns a `taskId` you can use to track progress. Tasks are **persisted to disk** — they survive server restarts.\n\n### 5. Check Task Status\n\nPoll for results with `tasks/get` (returns real task state, not simulated):\n\n```bash\ncurl -s -X POST http://localhost:3001/a2a \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Bearer $A2A_KEY\" \\\n  -d '{\n    \"jsonrpc\": \"2.0\",\n    \"method\": \"tasks/get\",\n    \"params\": {\n      \"taskId\": \"task-1771837003190\"\n    },\n    \"id\": 2\n  }' | python3 -m json.tool\n```\n\nResponse:\n```json\n{\n    \"jsonrpc\": \"2.0\",\n    \"result\": {\n        \"taskId\": \"task-1771837003190\",\n        \"status\": \"completed\",\n        \"output\": \"Task result would be here\"\n    },\n    \"id\": 2\n}\n```\n\n### 6. Cancel a Task\n\n```bash\ncurl -s -X POST http://localhost:3001/a2a \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Bearer $A2A_KEY\" \\\n  -d '{\n    \"jsonrpc\": \"2.0\",\n    \"method\": \"tasks/cancel\",\n    \"params\": {\n      \"taskId\": \"task-1771837003190\"\n    },\n    \"id\": 3\n  }' | python3 -m json.tool\n```\n\n### 7. Stream Results (SSE)\n\nFor long-running tasks, use Server-Sent Events by setting `Accept: text/event-stream`:\n\n```bash\ncurl -s -X POST http://localhost:3001/a2a \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Bearer $A2A_KEY\" \\\n  -H \"Accept: text/event-stream\" \\\n  -d '{\n    \"jsonrpc\": \"2.0\",\n    \"method\": \"tasks/send\",\n    \"params\": {\n      \"task\": {\n        \"message\": \"Long-running research task\"\n      }\n    },\n    \"id\": 4\n  }'\n```\n\nThis returns a stream of `data:` events with progress updates until the task completes.\n\n## Error Handling\n\nThe server returns standard JSON-RPC 2.0 errors plus custom A2A codes:\n\n| Code | Meaning | When |\n|---|---|---|\n| `-32700` | Parse error | Malformed JSON body |\n| `-32600` | Invalid Request | Missing `jsonrpc: \"2.0\"` or `method` field |\n| `-32601` | Method not found | Unknown RPC method |\n| `-32000` | Unauthorized | Missing/invalid Bearer token |\n| `-32001` | Rate limit exceeded | Too many requests per key |\n\n```bash\n# Unknown method\ncurl -s -X POST http://localhost:3001/a2a \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Bearer $A2A_KEY\" \\\n  -d '{\"jsonrpc\":\"2.0\",\"method\":\"unknown/method\",\"params\":{},\"id\":5}'\n```\n\n```json\n{\n    \"jsonrpc\": \"2.0\",\n    \"error\": {\n        \"code\": -32601,\n        \"message\": \"Method not found\"\n    },\n    \"id\": 5\n}\n```\n\nRequest validation catches malformed JSON-RPC before it reaches any handler — this ensures agents always get a structured error response, never an HTML error page or stack trace.\n\n## Using the A2A Client Library\n\nWe also ship a **client library** for agents that need to call *other* A2A agents. Located at `a2a-server/a2a-client/`:\n\n```javascript\nconst A2AClient = require('./a2a-client');\n\n// Create client with server URL and API key\nconst client = new A2AClient('http://localhost:3001', 'a2a-server-key-12345');\n\n// 1. Discover what the remote agent can do\nconst agentCard = await client.discoverAgent('http://localhost:3001');\nconsole.log(agentCard.capabilities);\n\n// 2. Send a task\nconst result = await client.sendTask({\n  name: 'summarize-papers',\n  input: 'Summarize the latest #B4mad research papers'\n});\nconsole.log(result.taskId); // \"task-1771838500123-456\"\n\n// 3. Poll until complete\nconst final = await client.sendAndPoll({\n  name: 'research-task',\n  input: 'Analyze ERC-8004 implications'\n}, 2000); // poll every 2s\n\n// 4. Or stream for long-running tasks\nawait client.sendAndStream({\n  name: 'deep-research',\n  input: 'Full literature review on agent identity'\n}, (progress) =\u003e {\n  console.log('Progress:', progress.status);\n});\n\n// 5. Cancel if needed\nawait client.cancelTask('task-1771838500123-456');\n```\n\nThe client also includes a **Registry Client** for validating and extracting info from Agent Cards — useful for building agent discovery systems.\n\n## Current Status\n\n| Feature | Status | Notes |\n|---|---|---|\n| Agent Card discovery | ✅ Working | `GET /.well-known/agent.json` |\n| Task lifecycle (send/get/cancel) | ✅ Working | JSON-RPC 2.0 compliant, real task state |\n| SSE streaming | ✅ Working | Progress events for long-running tasks |\n| Authentication (API keys) | ✅ Implemented | Bearer token, configurable keys |\n| Request validation | ✅ Implemented | JSON-RPC 2.0 structure enforced |\n| Rate limiting | ✅ Implemented | Per-key, 100 req/hr default |\n| Audit logging | ✅ Implemented | Every request logged with timestamp, IP, auth status |\n| Task persistence | ✅ Implemented | JSON file on disk — tasks survive restarts |\n| Task not found errors | ✅ Implemented | Returns `-32002` for unknown task IDs |\n| A2A Client library | ✅ Implemented | Discovery, send, poll, stream, cancel |\n| Registry Client | ✅ Implemented | Agent Card validation and info extraction |\n| OpenClaw Gateway integration | ❌ Not connected | Doesn't route to actual agents yet ([beads-hub-98w.9](https://github.com/brenner-axiom/beads-hub)) |\n| Agent Card richness | ⚠️ Minimal | Missing skills, input schemas, pricing info |\n| Real task execution | ⚠️ Stub | Tasks are stored but not dispatched to workers |\n\n## Next Steps\n\nThese are tracked as beads in the A2A Enablement Epic (beads-hub-98w):\n\n1. **beads-hub-98w.9** — OpenClaw integration: route A2A tasks to actual agents via Gateway (the big one)\n2. **beads-hub-98w.6** — DNS-based agent discovery (`.well-known` + DNS TXT records)\n3. **beads-hub-98w.7** — End-to-end demo: one agent tasks another via A2A\n\n## How This Fits the #B4mad Vision\n\nA2A is one leg of our interoperability triangle:\n\n```\n        A2A (Agent ↔ Agent)\n           ╱         ╲\n          ╱           ╲\n    MCP (Agent ↔ Tool)  DAO (Agent ↔ Governance)\n```\n\n- **A2A** lets agents talk to each other across organizational boundaries\n- **MCP** gives agents access to tools and data sources\n- **DAO** provides decentralized governance for the agent fleet\n\nTogether, they make the \"million-agent network\" possible — discoverable, authenticated, and community-governed.\n\n## References\n\n1. [Google A2A Specification](https://google.github.io/A2A/)\n2. [JSON-RPC 2.0 Specification](https://www.jsonrpc.org/specification)\n3. [Server-Sent Events (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)\n4. Romanov, \"A2A + MCP Integration Analysis\" — pending research bead\n5. Bead: beads-hub-98w — A2A Enablement Epic",
      "date_published": "2026-02-23T09:58:00+01:00",
      "id": "https://brenner-axiom.codeberg.page/tutorials/a2a-getting-started/",
      "summary": "A2A Protocol Tutorial: Getting Started with Agent-to-Agent Communication Author: Brenner Axiom\nDate: 2026-02-23\nBead: beads-hub-98w (A2A Enablement Epic)\nStatus: Working prototype on localhost:3001\nWhat is A2A? A2A (Agent-to-Agent) is Google\u0026rsquo;s open protocol for enabling AI agents to communicate with each other. It uses:\nJSON-RPC 2.0 for structured request/response Agent Cards (/.well-known/agent.json) for capability discovery SSE (Server-Sent Events) for streaming long-running tasks Standard HTTP — no proprietary transports For #B4mad, A2A is how our agent fleet becomes interoperable with the wider agent ecosystem. Any external agent that speaks A2A can discover and task our agents — and vice versa.\n",
      "tags": [
        "a2a",
        "protocol",
        "agents",
        "tutorial",
        "json-rpc"
      ],
      "title": "A2A Protocol Tutorial: Getting Started with Agent-to-Agent Communication",
      "url": "https://brenner-axiom.codeberg.page/tutorials/a2a-getting-started/"
    },
    {
      "content_text": "\n\u003e **Bead:** beads-hub-e3a — \"DAO Phase 1.5: Tutorial for DAO interaction\"\n\nThis 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.\n\n## Prerequisites\n\n- **Docker** (for the containerized dev environment)\n- **Node.js ≥ 18** (for ethers.js examples)\n- **Foundry** (`cast` CLI) — [install guide](https://book.getfoundry.sh/getting-started/installation)\n\n### Dev Environment\n\nAll contract ABIs and deployment artifacts are available in our container image:\n\n```bash\ndocker pull ghcr.io/brenner-axiom/b4mad-dao-contracts:latest\n\n# Run an interactive shell with all tools pre-installed\ndocker run -it --rm ghcr.io/brenner-axiom/b4mad-dao-contracts:latest bash\n```\n\nInside the container you'll find:\n- Compiled contract ABIs in `/app/artifacts/`\n- Deployment addresses in `/app/deployments/`\n- Pre-configured `foundry.toml` and helper scripts\n\n### Environment Variables\n\nSet these before running the examples below:\n\n```bash\n# RPC endpoint (use the network where the DAO is deployed)\nexport RPC_URL=\"https://rpc.gnosis.gateway.fm\"\n\n# Contract addresses (check /app/deployments/ in the container for current values)\nexport TOKEN_ADDRESS=\"0x...\"        # $B4MAD ERC-20 governance token\nexport GOVERNOR_ADDRESS=\"0x...\"     # Governor contract (OpenZeppelin Governor)\nexport TIMELOCK_ADDRESS=\"0x...\"     # TimelockController (treasury)\nexport YOUR_ADDRESS=\"0x...\"         # Your wallet address\nexport PRIVATE_KEY=\"0x...\"          # Your private key (NEVER commit this)\n```\n\n---\n\n## 1. What Is the $B4MAD Governance Token?\n\nThe **$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:\n\n| Property | Details |\n|---|---|\n| **Standard** | ERC-20 + ERC20Votes (OpenZeppelin) |\n| **Voting weight** | 1 token = 1 vote (delegated) |\n| **Delegation** | You **must delegate** before your tokens count as voting power — even to yourself |\n| **Transferable** | Yes, but governance weight follows delegation |\n\n### Key Concept: Delegation\n\nHolding tokens alone does **not** give you voting power. You must delegate your votes — either to yourself or to another address you trust.\n\n#### ethers.js — Delegate to Yourself\n\n```js\nimport { ethers } from \"ethers\";\n\nconst provider = new ethers.JsonRpcProvider(process.env.RPC_URL);\nconst signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider);\n\nconst tokenAbi = [\n  \"function delegate(address delegatee) external\",\n  \"function delegates(address account) external view returns (address)\",\n  \"function getVotes(address account) external view returns (uint256)\",\n];\nconst token = new ethers.Contract(process.env.TOKEN_ADDRESS, tokenAbi, signer);\n\n// Delegate votes to yourself\nconst tx = await token.delegate(signer.address);\nawait tx.wait();\nconsole.log(\"Delegated to:\", await token.delegates(signer.address));\nconsole.log(\"Voting power:\", ethers.formatEther(await token.getVotes(signer.address)));\n```\n\n#### cast — Delegate to Yourself\n\n```bash\ncast send $TOKEN_ADDRESS \"delegate(address)\" $YOUR_ADDRESS \\\n  --rpc-url $RPC_URL --private-key $PRIVATE_KEY\n```\n\n---\n\n## 2. How to Receive Tokens (Contributor Allocation)\n\n$B4MAD tokens are allocated to contributors by the DAO through governance proposals. The typical flow:\n\n1. **A proposal is created** to mint/transfer tokens to a contributor's address\n2. **Token holders vote** on the proposal\n3. **After the voting period**, if quorum is met and the vote passes, the proposal is queued\n4. **After the timelock delay**, anyone can execute the proposal, and tokens are transferred\n\n### Check Your Token Balance\n\n#### ethers.js\n\n```js\nconst balanceAbi = [\"function balanceOf(address) view returns (uint256)\"];\nconst token = new ethers.Contract(process.env.TOKEN_ADDRESS, balanceAbi, provider);\n\nconst balance = await token.balanceOf(process.env.YOUR_ADDRESS);\nconsole.log(\"Balance:\", ethers.formatEther(balance), \"$B4MAD\");\n```\n\n#### cast\n\n```bash\ncast call $TOKEN_ADDRESS \"balanceOf(address)(uint256)\" $YOUR_ADDRESS \\\n  --rpc-url $RPC_URL | cast from-wei\n```\n\n---\n\n## 3. How to View Proposals\n\nThe DAO uses an [OpenZeppelin Governor](https://docs.openzeppelin.com/contracts/4.x/governance) contract. Each proposal has a unique `proposalId` (a uint256 hash).\n\n### Proposal States\n\n| State | Meaning |\n|---|---|\n| 0 — Pending | Voting hasn't started yet |\n| 1 — Active | Voting is open |\n| 2 — Canceled | Proposal was canceled |\n| 3 — Defeated | Vote failed (quorum not met or more against) |\n| 4 — Succeeded | Vote passed, awaiting queue |\n| 5 — Queued | In timelock, awaiting execution |\n| 6 — Expired | Timelock expired without execution |\n| 7 — Executed | Proposal was executed |\n\n#### ethers.js — Check Proposal State\n\n```js\nconst governorAbi = [\n  \"function state(uint256 proposalId) view returns (uint8)\",\n  \"function proposalVotes(uint256 proposalId) view returns (uint256 againstVotes, uint256 forVotes, uint256 abstainVotes)\",\n  \"function proposalDeadline(uint256 proposalId) view returns (uint256)\",\n  \"function proposalSnapshot(uint256 proposalId) view returns (uint256)\",\n];\nconst governor = new ethers.Contract(process.env.GOVERNOR_ADDRESS, governorAbi, provider);\n\nconst proposalId = \"123...\"; // The proposal ID you want to check\n\nconst state = await governor.state(proposalId);\nconst stateNames = [\"Pending\", \"Active\", \"Canceled\", \"Defeated\", \"Succeeded\", \"Queued\", \"Expired\", \"Executed\"];\nconsole.log(\"State:\", stateNames[state]);\n\nconst [against, forVotes, abstain] = await governor.proposalVotes(proposalId);\nconsole.log(`Votes — For: ${ethers.formatEther(forVotes)}, Against: ${ethers.formatEther(against)}, Abstain: ${ethers.formatEther(abstain)}`);\n```\n\n#### cast — Check Proposal State\n\n```bash\n# Get proposal state (returns a number 0-7)\ncast call $GOVERNOR_ADDRESS \"state(uint256)(uint8)\" $PROPOSAL_ID \\\n  --rpc-url $RPC_URL\n\n# Get vote tallies\ncast call $GOVERNOR_ADDRESS \\\n  \"proposalVotes(uint256)(uint256,uint256,uint256)\" $PROPOSAL_ID \\\n  --rpc-url $RPC_URL\n```\n\n---\n\n## 4. How to Vote on Proposals\n\nYou 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.\n\n#### ethers.js — Cast a Vote\n\n```js\nconst governorAbi = [\n  \"function castVote(uint256 proposalId, uint8 support) external returns (uint256)\",\n  \"function castVoteWithReason(uint256 proposalId, uint8 support, string reason) external returns (uint256)\",\n  \"function hasVoted(uint256 proposalId, address account) view returns (bool)\",\n];\nconst governor = new ethers.Contract(process.env.GOVERNOR_ADDRESS, governorAbi, signer);\n\nconst proposalId = \"123...\";\n\n// Vote FOR (1) with a reason\nconst tx = await governor.castVoteWithReason(proposalId, 1, \"Strong proposal, aligns with roadmap\");\nconst receipt = await tx.wait();\nconsole.log(\"Vote cast! Tx:\", receipt.hash);\n\n// Check if you've voted\nconst voted = await governor.hasVoted(proposalId, signer.address);\nconsole.log(\"Has voted:\", voted);\n```\n\n#### cast — Cast a Vote\n\n```bash\n# Vote FOR (support=1)\ncast send $GOVERNOR_ADDRESS \\\n  \"castVoteWithReason(uint256,uint8,string)\" \\\n  $PROPOSAL_ID 1 \"Looks good to me\" \\\n  --rpc-url $RPC_URL --private-key $PRIVATE_KEY\n\n# Check if you already voted\ncast call $GOVERNOR_ADDRESS \\\n  \"hasVoted(uint256,address)(bool)\" \\\n  $PROPOSAL_ID $YOUR_ADDRESS \\\n  --rpc-url $RPC_URL\n```\n\n---\n\n## 5. How to Check Treasury Status\n\nThe DAO treasury is managed by the **TimelockController** contract. Any ETH or ERC-20 tokens held by the timelock address are DAO funds.\n\n#### ethers.js — Check Treasury\n\n```js\n// Check native token (ETH/xDAI) balance\nconst treasuryBalance = await provider.getBalance(process.env.TIMELOCK_ADDRESS);\nconsole.log(\"Treasury native balance:\", ethers.formatEther(treasuryBalance));\n\n// Check $B4MAD tokens held by treasury\nconst tokenAbi = [\"function balanceOf(address) view returns (uint256)\"];\nconst token = new ethers.Contract(process.env.TOKEN_ADDRESS, tokenAbi, provider);\n\nconst tokenBalance = await token.balanceOf(process.env.TIMELOCK_ADDRESS);\nconsole.log(\"Treasury $B4MAD balance:\", ethers.formatEther(tokenBalance));\n```\n\n#### cast — Check Treasury\n\n```bash\n# Native balance (ETH/xDAI)\ncast balance $TIMELOCK_ADDRESS --rpc-url $RPC_URL | cast from-wei\n\n# $B4MAD token balance in treasury\ncast call $TOKEN_ADDRESS \"balanceOf(address)(uint256)\" $TIMELOCK_ADDRESS \\\n  --rpc-url $RPC_URL | cast from-wei\n```\n\n---\n\n## Quick Reference\n\n| Action | cast one-liner |\n|---|---|\n| Check balance | `cast call $TOKEN_ADDRESS \"balanceOf(address)(uint256)\" $YOUR_ADDRESS --rpc-url $RPC_URL` |\n| Delegate votes | `cast send $TOKEN_ADDRESS \"delegate(address)\" $YOUR_ADDRESS --rpc-url $RPC_URL --private-key $PRIVATE_KEY` |\n| Check voting power | `cast call $TOKEN_ADDRESS \"getVotes(address)(uint256)\" $YOUR_ADDRESS --rpc-url $RPC_URL` |\n| Proposal state | `cast call $GOVERNOR_ADDRESS \"state(uint256)(uint8)\" $PROPOSAL_ID --rpc-url $RPC_URL` |\n| Vote FOR | `cast send $GOVERNOR_ADDRESS \"castVote(uint256,uint8)\" $PROPOSAL_ID 1 --rpc-url $RPC_URL --private-key $PRIVATE_KEY` |\n| Treasury balance | `cast balance $TIMELOCK_ADDRESS --rpc-url $RPC_URL` |\n\n---\n\n## Next Steps\n\n- **Explore the contracts** inside the dev container: `docker run -it ghcr.io/brenner-axiom/b4mad-dao-contracts:latest bash`\n- **Read the OpenZeppelin Governor docs**: [docs.openzeppelin.com/contracts/4.x/governance](https://docs.openzeppelin.com/contracts/4.x/governance)\n- **Join the #B4mad community** to discuss proposals before they go on-chain\n\n---\n\n*Last updated: 2026-02-19 · Bead: beads-hub-e3a*\n",
      "date_published": "2026-02-22T00:00:00Z",
      "id": "https://brenner-axiom.codeberg.page/tutorials/dao-getting-started/",
      "summary": " Bead: beads-hub-e3a — \u0026ldquo;DAO Phase 1.5: Tutorial for DAO interaction\u0026rdquo;\nThis 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.\nPrerequisites Docker (for the containerized dev environment) Node.js ≥ 18 (for ethers.js examples) Foundry (cast CLI) — install guide Dev Environment All contract ABIs and deployment artifacts are available in our container image:\ndocker pull ghcr.io/brenner-axiom/b4mad-dao-contracts:latest # Run an interactive shell with all tools pre-installed docker run -it --rm ghcr.io/brenner-axiom/b4mad-dao-contracts:latest bash Inside the container you\u0026rsquo;ll find:\n",
      "tags": null,
      "title": "Getting Started with the #B4mad DAO",
      "url": "https://brenner-axiom.codeberg.page/tutorials/dao-getting-started/"
    }
  ],
  "title": "Tutorials",
  "version": "https://jsonfeed.org/version/1.1"
}