Claude Code for Compound Governance (2026)
Claude Code for Compound Governance Workflow
Automating decentralized governance is one of the most powerful use cases for Claude Code in the Web3 space. Compound’s governance model, which relies on proposers, delegates, and executors, lends itself perfectly to Claude-assisted automation. This guide walks you through building a complete governance workflow using Claude Code, from drafting proposals to executing passed measures.
Understanding Compound Governance Architecture
Compound’s governance operates on a three-phase model: proposal submission, voting period, and execution. The protocol uses COMP tokens as voting power, with delegates able to cast votes on behalf of token holders. Before automating, you need to understand how these components interact:
- Proposal Contract: The on-chain entity that holds the executable code
- Governor Bravo: The governance contract that manages voting thresholds and timing
- Timelock Controller: The contract that enforces a delay between proposal passage and execution
Claude Code can interact with all three through a combination of RPC calls and local file operations for proposal drafting.
Setting Up Your Governance Skill
Create a specialized skill for governance operations. This skill should handle both the off-chain drafting and on-chain interaction aspects:
---
name: compound-governance
description: Manage Compound governance proposals and voting
---
This skill declaration restricts tool access to file operations and bash, enough to draft proposals locally while keeping governance calls isolated in executable scripts.
Proposal Drafting Workflow
The first governance task Claude Code excels at is proposal drafting. Rather than manually writing Solidity and configuration files, you can automate the entire drafting process:
Step 1: Define Proposal Parameters
Create a configuration-driven approach where proposal details live in structured files:
{
"proposal": {
"title": "Increase USDC Collateral Factor",
"description": "Increase the collateral factor for USDC from 75% to 80% to improve capital efficiency.",
"targets": ["0x39AA39c419df9D5B5A9D46f1bA1dD4e2bF3d8E1c"],
"values": ["0"],
"signatures": ["_setCollateralFactor(address,uint256)"],
"calldatas": ["0x000000000000000000000000A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480000000000000000000000000000000000000000000000000000000000000050"],
"voteStart": 1682000000,
"voteDuration": 199080
}
}
Step 2: Generate Proposal Script
Use Claude Code to generate the executable JavaScript that submits your proposal:
const { ethers } = require('ethers');
const fs = require('fs');
async function propose(config) {
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
const governor = new ethers.Contract(
config.governanceAddress,
['function propose(address[],uint256[],string[],bytes[],string) returns (uint256)'],
wallet
);
const tx = await governor.propose(
config.proposal.targets,
config.proposal.values,
config.proposal.signatures,
config.proposal.calldatas,
config.proposal.description
);
const receipt = await tx.wait();
console.log(`Proposal created: ${receipt.hash}`);
return receipt;
}
module.exports = { propose };
Step 3: Automate Execution
Combine everything into a bash script that Claude Code can execute:
#!/bin/bash
Governance proposal submission script
CONFIG_FILE=$1
PROPOSAL_ID=$(node submit-proposal.js "$CONFIG_FILE")
echo "Proposal ID: $PROPOSAL_ID"
Voting Automation with Delegates
Once proposals exist, Claude Code can help manage voting through delegate accounts. This is particularly useful for protocols where you hold voting power across multiple wallets.
Delegate Voting Pattern
class GovernanceVoter {
constructor(delegates, governorAddress) {
this.delegates = delegates;
this.governor = new ethers.Contract(governorAddress,GovernorABI,provider);
}
async castVote(proposalId, voteType) {
// voteType: 0=Against, 1=For, 2=Abstain
const results = await Promise.all(
this.delegates.map(async (delegate) => {
const wallet = new ethers.Wallet(delegate.privateKey, provider);
const contract = this.governor.connect(wallet);
const tx = await contract.castVote(proposalId, voteType);
return tx.hash;
})
);
return results;
}
}
The key insight here is that Claude Code can maintain a secure configuration of delegate keys (never committing them to version control) and execute voting across all accounts with a single command.
Monitoring and Execution
The final piece of the governance workflow is monitoring proposal status and executing passed measures. Claude Code can continuously monitor chain state:
async function checkProposalState(proposalId) {
const state = await governor.state(proposalId);
const states = ['Pending', 'Active', 'Canceled', 'Defeated', 'Succeeded', 'Queued', 'Expired', 'Executed'];
console.log(`Proposal ${proposalId}: ${states[state]}`);
if (state === 4) { // Succeeded
await queueForTimelock(proposalId);
}
}
For continuous monitoring, set up a simple cron job that Claude Code can manage:
Run every 5 minutes to check active proposals
*/5 * * * * cd /path/to/governance && node monitor-proposals.js >> /var/log/governance.log
Queuing and Executing Passed Proposals
When a proposal reaches the Succeeded state, it needs to be queued in the Timelock before it can be executed. This is a separate on-chain transaction that starts the mandatory delay clock. Claude Code can automate both steps as part of a continuous monitoring loop:
async function queueForTimelock(proposalId) {
const tx = await governor.queue(proposalId);
const receipt = await tx.wait();
console.log(`Proposal ${proposalId} queued: ${receipt.hash}`);
// Calculate when execution becomes available
const delay = await timelock.delay();
const executableAt = Date.now() + (Number(delay) * 1000);
console.log(`Executable at: ${new Date(executableAt).toISOString()}`);
// Store state for later execution
const state = {
proposalId,
executableAt,
status: 'queued'
};
require('fs').writeFileSync(`./state/proposal-${proposalId}.json`, JSON.stringify(state, null, 2));
}
async function executeWhenReady(proposalId) {
const statePath = `./state/proposal-${proposalId}.json`;
const state = JSON.parse(require('fs').readFileSync(statePath, 'utf8'));
if (Date.now() < state.executableAt) {
const remaining = Math.ceil((state.executableAt - Date.now()) / 1000 / 60);
console.log(`Not yet executable. ${remaining} minutes remaining`);
return;
}
const tx = await governor.execute(proposalId);
const receipt = await tx.wait();
console.log(`Proposal ${proposalId} executed: ${receipt.hash}`);
state.status = 'executed';
require('fs').writeFileSync(statePath, JSON.stringify(state, null, 2));
}
Writing state to disk between runs means your monitoring loop survives restarts without losing track of where proposals stand in the lifecycle.
Calldata Encoding and Validation
One of the most error-prone parts of governance is encoding calldata correctly. A single byte offset wrong means your proposal passes and then silently does nothing, or worse, executes against the wrong parameters. Claude Code can generate and verify calldata before submission:
const { ethers } = require('ethers');
function encodeCollateralFactor(tokenAddress, factorBips) {
// Governor Bravo expects ABI-encoded calldata without the function selector
// because the selector is passed separately in the signatures array
const abiCoder = ethers.AbiCoder.defaultAbiCoder();
const encoded = abiCoder.encode(
['address', 'uint256'],
[tokenAddress, factorBips]
);
return encoded;
}
async function validateCalldata(targetAddress, signature, calldata) {
// Simulate the call locally before submitting the proposal
const iface = new ethers.Interface([`function ${signature}`]);
const fullCalldata = iface.encodeFunctionData(
signature.split('(')[0],
iface.decodeFunctionData(signature.split('(')[0], '0x' + calldata.slice(2))
);
try {
await provider.call({
to: targetAddress,
data: fullCalldata
});
console.log('Calldata validation: PASSED');
return true;
} catch (err) {
console.error('Calldata validation FAILED:', err.reason || err.message);
return false;
}
}
// Example: validate before proposing
const calldata = encodeCollateralFactor(
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
800 // 80% expressed as basis points out of 1000
);
const isValid = await validateCalldata(
'0x39AA39c419df9D5B5A9D46f1bA1dD4e2bF3d8E1c',
'_setCollateralFactor(address,uint256)',
calldata
);
if (!isValid) {
process.exit(1); // Never submit proposals with invalid calldata
}
Run this validation as part of your CI pipeline so bad calldata never reaches the chain.
Building a Governance Audit Trail
Governance decisions are permanent and public. Keeping a local audit trail that correlates on-chain events with the off-chain intent behind them is invaluable for post-mortems and protocol transparency reports.
Add a logging layer to every governance operation:
const fs = require('fs');
const path = require('path');
const AUDIT_LOG = path.resolve('./audit/governance-log.jsonl');
function auditLog(event) {
const entry = {
timestamp: new Date().toISOString(),
blockNumber: event.blockNumber || null,
txHash: event.txHash || null,
type: event.type,
proposalId: event.proposalId,
actor: event.actor,
details: event.details
};
fs.appendFileSync(AUDIT_LOG, JSON.stringify(entry) + '\n');
}
// Usage after each operation
auditLog({
type: 'PROPOSAL_SUBMITTED',
proposalId: '42',
actor: wallet.address,
txHash: receipt.hash,
blockNumber: receipt.blockNumber,
details: { title: 'Increase USDC Collateral Factor', calldataHash: ethers.keccak256(calldata) }
});
The JSONL format (one JSON object per line) makes it easy to grep specific proposal IDs or event types without loading the entire log into memory.
Multi-Protocol Governance with Shared Skill Infrastructure
Once you have a working governance skill for Compound, the same architecture extends to any Governor Bravo-compatible protocol: Uniswap, Aave (with minor adapter changes), and Compound forks. The key is parameterizing the contract addresses and ABIs rather than hardcoding them:
// governance-config.js. supports multiple protocols
const PROTOCOLS = {
compound: {
governor: '0xc0Da02939E1441F497fd74F78cE7Decb17B66529',
timelock: '0x6d903f6003cca6255D85CcA4D3B5E5146dC33925',
token: '0xc00e94Cb662C3520282E6f5717214004A7f26888',
votingDelay: 13140, // blocks
votingPeriod: 19710, // blocks
proposalThreshold: '100000000000000000000000' // 100,000 COMP
},
uniswap: {
governor: '0x408ED6354d4973f66138C91495F2f2FCbd8724C3',
timelock: '0x1a9C8182C09F50C8318d769245beA52c32BE35BC',
token: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984',
votingDelay: 1,
votingPeriod: 40320,
proposalThreshold: '2500000000000000000000000' // 2.5M UNI
}
};
module.exports = { PROTOCOLS };
Your Claude Code skill can accept a --protocol flag and load the appropriate configuration, making the same skill reusable across your entire governance portfolio.
Security Considerations
When automating governance with Claude Code, follow these security practices:
- Never commit private keys: Use environment variables or a secrets manager like AWS Secrets Manager or HashiCorp Vault
- Multi-sig for execution: Configure your timelock to require multiple signatures using a Gnosis Safe as the executor
- Timelock verification: Always verify the timelock delay before execution, protocol upgrades can change it
- Proposal simulation: Test calldata with
eth_callbefore submitting, as shown in the validation section above - Dry-run mode: Add a
DRY_RUN=trueenvironment variable that logs what would happen without sending transactions - Rotate keys regularly: Delegate-voting keys should be rotated on a schedule; automate key rotation with your secrets manager
A practical dry-run implementation keeps the same code path but skips the actual tx.wait():
async function submitProposal(config) {
if (process.env.DRY_RUN === 'true') {
console.log('[DRY RUN] Would submit proposal:', JSON.stringify(config, null, 2));
return { hash: '0x-dry-run', blockNumber: null };
}
const tx = await governor.propose(/* ... */);
return tx.wait();
}
Actionable Summary
To implement a complete Compound governance workflow with Claude Code:
- Create a governance skill with file and bash tool access
- Define proposals in structured JSON configuration files
- Build submission scripts that interact with Governor contracts
- Validate calldata with
eth_callsimulations before submitting - Implement delegate voting automation for multi-wallet governance
- Set up monitoring for proposal state transitions, queue, and execution
- Write every operation to a JSONL audit log for transparency
- Parameterize protocol configs to reuse the same skill across Governor Bravo forks
- Always follow security best practices for key management and use dry-run mode during development
Claude Code transforms governance from a manual, error-prone process into a reliable, auditable workflow. By treating governance operations as code-configured actions, you gain repeatability and safety in protocol management.
Try it: Paste your error into our Error Diagnostic for an instant fix.
Related Reading
- Claude Code for OSS Governance Workflow Tutorial Guide
- AI Assisted Architecture Design Workflow Guide
- AI Assisted Code Review Workflow Best Practices
Built by theluckystrike. More at zovo.one
Find the right skill → Browse 155+ skills in our Skill Finder.