Smart Contracts
- Smart Contracts Reference
- Core Contracts
- Optional Modules
- Architecture
Integration Guides
Contract Interaction
Guide to programmatically interacting with CreateDAO contracts
Contract Interaction
This guide explains how to programmatically interact with CreateDAO contracts using ethers.js or viem. It covers reading data from contracts, writing transactions, handling events, and implementing common use cases.
Prerequisites
Before you begin, make sure you have:
- Basic knowledge of JavaScript/TypeScript
- Familiarity with Ethereum and smart contracts
- Node.js and npm/yarn installed
Setting Up
1. Install Dependencies
# Using npm
npm install ethers
# Using yarn
yarn add ethers
2. Configure Provider
Set up a provider to connect to the blockchain:
import { ethers } from 'ethers';
// For browser environments with MetaMask
const provider = new ethers.providers.Web3Provider(window.ethereum);
// For Node.js environments
// const provider = new ethers.providers.JsonRpcProvider('https://mainnet.infura.io/v3/YOUR_INFURA_KEY');
3. Load Contract ABIs
Create a file to store your contract ABIs:
// abis.js
export const DAO_FACTORY_ABI = [...]; // DAO Factory ABI
export const DAO_ABI = [...]; // DAO ABI
export const DAO_TOKEN_ABI = [...]; // DAO Token ABI
export const DAO_TREASURY_ABI = [...]; // DAO Treasury ABI
export const DAO_STAKING_ABI = [...]; // DAO Staking ABI
export const DAO_PRESALE_ABI = [...]; // DAO Presale ABI
Reading Contract Data
Connecting to Contracts
import { ethers } from 'ethers';
import { DAO_FACTORY_ABI, DAO_ABI } from './abis';
// Connect to DAO Factory
const daoFactoryAddress = '0x...'; // Replace with actual address
const daoFactory = new ethers.Contract(daoFactoryAddress, DAO_FACTORY_ABI, provider);
// Connect to a specific DAO
const daoAddress = '0x...'; // Replace with actual address
const dao = new ethers.Contract(daoAddress, DAO_ABI, provider);
Reading Basic DAO Information
async function getDAOInfo(daoAddress) {
const dao = new ethers.Contract(daoAddress, DAO_ABI, provider);
// Get DAO name
const name = await dao.name();
// Get associated contract addresses
const tokenAddress = await dao.upgradeableContracts(2); // 2 is the index for Token in the UpgradeableContract enum
const treasuryAddress = await dao.upgradeableContracts(1); // 1 is the index for Treasury
const stakingAddress = await dao.upgradeableContracts(0); // 0 is the index for Staking
// Get governance parameters
const votingPeriod = await dao.votingPeriod();
const minProposalStake = await dao.minProposalStake();
const quorum = await dao.quorum();
return {
name,
tokenAddress,
treasuryAddress,
stakingAddress,
governance: {
votingPeriod: votingPeriod.toString(),
minProposalStake: ethers.utils.formatEther(minProposalStake),
quorum: quorum.toNumber() / 100, // Convert basis points to percentage
}
};
}
Reading Proposal Data
async function getProposalDetails(daoAddress, proposalId) {
const dao = new ethers.Contract(daoAddress, DAO_ABI, provider);
// Get basic proposal info
const proposal = await dao.getProposal(proposalId);
const [proposalType, forVotes, againstVotes, endTime, executed] = proposal;
// Get proposal-specific data based on type
let specificData = {};
if (proposalType.toNumber() === 0) { // Transfer proposal
const transferData = await dao.getTransferData(proposalId);
specificData = {
token: transferData.token,
recipient: transferData.recipient,
amount: ethers.utils.formatEther(transferData.amount),
};
} else if (proposalType.toNumber() === 1) { // Upgrade proposal
const upgradeData = await dao.getUpgradeData(proposalId);
specificData = {
newVersion: upgradeData.newVersion,
};
}
// Add other proposal types as needed
return {
id: proposalId,
type: proposalType.toNumber(),
votes: {
for: ethers.utils.formatEther(forVotes),
against: ethers.utils.formatEther(againstVotes),
},
endTime: new Date(endTime.toNumber() * 1000),
executed,
specificData,
};
}
Reading Treasury Data
async function getTreasuryInfo(treasuryAddress) {
const treasury = new ethers.Contract(treasuryAddress, DAO_TREASURY_ABI, provider);
// Get ETH balance
const ethBalance = await treasury.getETHBalance();
// Get token balance (example for a specific token)
const tokenAddress = '0x...'; // Replace with actual token address
const tokenBalance = await treasury.getERC20Balance(tokenAddress);
return {
ethBalance: ethers.utils.formatEther(ethBalance),
tokenBalance: ethers.utils.formatEther(tokenBalance),
};
}
Reading Staking Data
async function getStakingInfo(stakingAddress, userAddress) {
const staking = new ethers.Contract(stakingAddress, DAO_STAKING_ABI, provider);
// Get total staked
const totalStaked = await staking.totalStaked();
// Get user's staked amount
const userStaked = await staking.stakedAmount(userAddress);
// Get user's voting power
const votingPower = await staking.getVotingPower(userAddress);
// Get staking multipliers
const multipliers = [];
for (let i = 0; i < 4; i++) {
multipliers.push((await staking.multipliers(i)).toNumber() / 10000);
}
// Get staking thresholds
const thresholds = [];
for (let i = 0; i < 3; i++) {
thresholds.push((await staking.thresholds(i)).toNumber());
}
return {
totalStaked: ethers.utils.formatEther(totalStaked),
userStaked: ethers.utils.formatEther(userStaked),
votingPower: ethers.utils.formatEther(votingPower),
multipliers,
thresholds: thresholds.map(t => t / (24 * 60 * 60) + ' days'),
};
}
Writing Transactions
Setting Up a Signer
// Browser environment with MetaMask
const provider = new ethers.providers.Web3Provider(window.ethereum);
await provider.send("eth_requestAccounts", []);
const signer = provider.getSigner();
// Node.js environment with private key
// const privateKey = 'your-private-key';
// const signer = new ethers.Wallet(privateKey, provider);
Creating a DAO
async function createDAO(
daoFactoryAddress,
versionId,
name,
tokenName,
tokenSymbol,
initialSupply
) {
const daoFactory = new ethers.Contract(daoFactoryAddress, DAO_FACTORY_ABI, signer);
// Convert initialSupply to wei (assuming 18 decimals)
const initialSupplyWei = ethers.utils.parseEther(initialSupply.toString());
// Estimate gas
const gasEstimate = await daoFactory.estimateGas.createDAO(
versionId,
name,
tokenName,
tokenSymbol,
initialSupplyWei
);
// Add 20% buffer to gas estimate
const gasLimit = gasEstimate.mul(120).div(100);
// Create DAO
const tx = await daoFactory.createDAO(
versionId,
name,
tokenName,
tokenSymbol,
initialSupplyWei,
{ gasLimit }
);
console.log(`Transaction submitted: ${tx.hash}`);
// Wait for transaction to be mined
const receipt = await tx.wait();
console.log(`Transaction confirmed in block ${receipt.blockNumber}`);
// Parse event logs to get deployed addresses
const daoCreatedEvent = receipt.events.find(e => e.event === 'DAOCreated');
if (daoCreatedEvent) {
const { daoAddress, tokenAddress, treasuryAddress, stakingAddress } = daoCreatedEvent.args;
return {
daoAddress,
tokenAddress,
treasuryAddress,
stakingAddress,
transactionHash: tx.hash,
};
}
throw new Error('Failed to parse DAOCreated event');
}
Creating a Proposal
async function createTransferProposal(daoAddress, tokenAddress, recipient, amount) {
const dao = new ethers.Contract(daoAddress, DAO_ABI, signer);
// Convert amount to wei (assuming 18 decimals)
const amountWei = ethers.utils.parseEther(amount.toString());
// Create proposal
const tx = await dao.proposeTransfer(tokenAddress, recipient, amountWei);
console.log(`Transaction submitted: ${tx.hash}`);
// Wait for transaction to be mined
const receipt = await tx.wait();
console.log(`Transaction confirmed in block ${receipt.blockNumber}`);
// Parse event logs to get proposal ID
const proposalCreatedEvent = receipt.events.find(e => e.event === 'ProposalCreated');
if (proposalCreatedEvent) {
const proposalId = proposalCreatedEvent.args.proposalId;
return {
proposalId: proposalId.toString(),
transactionHash: tx.hash,
};
}
throw new Error('Failed to parse ProposalCreated event');
}
Voting on a Proposal
async function voteOnProposal(daoAddress, proposalId, support) {
const dao = new ethers.Contract(daoAddress, DAO_ABI, signer);
// Vote on proposal
const tx = await dao.vote(proposalId, support);
console.log(`Transaction submitted: ${tx.hash}`);
// Wait for transaction to be mined
const receipt = await tx.wait();
console.log(`Transaction confirmed in block ${receipt.blockNumber}`);
return {
transactionHash: tx.hash,
blockNumber: receipt.blockNumber,
};
}
Executing a Proposal
async function executeProposal(daoAddress, proposalId) {
const dao = new ethers.Contract(daoAddress, DAO_ABI, signer);
// Execute proposal
const tx = await dao.execute(proposalId);
console.log(`Transaction submitted: ${tx.hash}`);
// Wait for transaction to be mined
const receipt = await tx.wait();
console.log(`Transaction confirmed in block ${receipt.blockNumber}`);
return {
transactionHash: tx.hash,
blockNumber: receipt.blockNumber,
};
}
Staking Tokens
async function stakeTokens(stakingAddress, tokenAddress, amount) {
const staking = new ethers.Contract(stakingAddress, DAO_STAKING_ABI, signer);
const token = new ethers.Contract(tokenAddress, DAO_TOKEN_ABI, signer);
// Convert amount to wei (assuming 18 decimals)
const amountWei = ethers.utils.parseEther(amount.toString());
// Approve tokens for staking
const approveTx = await token.approve(stakingAddress, amountWei);
console.log(`Approval transaction submitted: ${approveTx.hash}`);
// Wait for approval transaction to be mined
await approveTx.wait();
console.log('Approval confirmed');
// Stake tokens
const stakeTx = await staking.stake(amountWei);
console.log(`Stake transaction submitted: ${stakeTx.hash}`);
// Wait for stake transaction to be mined
const receipt = await stakeTx.wait();
console.log(`Stake transaction confirmed in block ${receipt.blockNumber}`);
return {
transactionHash: stakeTx.hash,
blockNumber: receipt.blockNumber,
};
}
Unstaking Tokens
async function unstakeTokens(stakingAddress, amount) {
const staking = new ethers.Contract(stakingAddress, DAO_STAKING_ABI, signer);
// Convert amount to wei (assuming 18 decimals)
const amountWei = ethers.utils.parseEther(amount.toString());
// Unstake tokens
const tx = await staking.unstake(amountWei);
console.log(`Transaction submitted: ${tx.hash}`);
// Wait for transaction to be mined
const receipt = await tx.wait();
console.log(`Transaction confirmed in block ${receipt.blockNumber}`);
return {
transactionHash: tx.hash,
blockNumber: receipt.blockNumber,
};
}
Handling Events
Listening for New Proposals
function listenForProposals(daoAddress) {
const dao = new ethers.Contract(daoAddress, DAO_ABI, provider);
// Listen for ProposalCreated events
dao.on('ProposalCreated', (proposalId, proposer, description, event) => {
console.log(`New proposal created: #${proposalId}`);
console.log(`Proposer: ${proposer}`);
console.log(`Description: ${description}`);
console.log(`Block: ${event.blockNumber}`);
// You can fetch additional details about the proposal here
});
return () => {
// Return a cleanup function to remove the listener
dao.removeAllListeners('ProposalCreated');
};
}
Listening for Votes
function listenForVotes(daoAddress) {
const dao = new ethers.Contract(daoAddress, DAO_ABI, provider);
// Listen for Voted events
dao.on('Voted', (voter, proposalId, support, weight, event) => {
console.log(`New vote on proposal #${proposalId}`);
console.log(`Voter: ${voter}`);
console.log(`Support: ${support ? 'For' : 'Against'}`);
console.log(`Weight: ${ethers.utils.formatEther(weight)}`);
console.log(`Block: ${event.blockNumber}`);
});
return () => {
// Return a cleanup function to remove the listener
dao.removeAllListeners('Voted');
};
}
Listening for Proposal Execution
function listenForExecution(daoAddress) {
const dao = new ethers.Contract(daoAddress, DAO_ABI, provider);
// Listen for ProposalExecuted events
dao.on('ProposalExecuted', (proposalId, event) => {
console.log(`Proposal #${proposalId} executed`);
console.log(`Block: ${event.blockNumber}`);
});
return () => {
// Return a cleanup function to remove the listener
dao.removeAllListeners('ProposalExecuted');
};
}
Common Use Cases
Fetching All DAOs Created by a Factory
async function getAllDAOs(daoFactoryAddress) {
const daoFactory = new ethers.Contract(daoFactoryAddress, DAO_FACTORY_ABI, provider);
// Get past DAOCreated events
const filter = daoFactory.filters.DAOCreated();
const events = await daoFactory.queryFilter(filter);
// Parse events to get DAO addresses
const daos = events.map(event => {
const { daoAddress, tokenAddress, treasuryAddress, stakingAddress, name, versionId } = event.args;
return {
daoAddress,
tokenAddress,
treasuryAddress,
stakingAddress,
name,
versionId,
blockNumber: event.blockNumber,
};
});
return daos;
}
Fetching All Proposals for a DAO
async function getAllProposals(daoAddress) {
const dao = new ethers.Contract(daoAddress, DAO_ABI, provider);
// Get proposal count
const proposalCount = await dao.proposalCount();
// Fetch all proposals
const proposals = [];
for (let i = 1; i <= proposalCount; i++) {
try {
const proposal = await getProposalDetails(daoAddress, i);
proposals.push(proposal);
} catch (error) {
console.error(`Error fetching proposal #${i}:`, error);
}
}
return proposals;
}
Checking if a User Can Create a Proposal
async function canCreateProposal(daoAddress, stakingAddress, userAddress) {
const dao = new ethers.Contract(daoAddress, DAO_ABI, provider);
const staking = new ethers.Contract(stakingAddress, DAO_STAKING_ABI, provider);
// Get minimum proposal stake
const minProposalStake = await dao.minProposalStake();
// Get user's voting power
const votingPower = await staking.getVotingPower(userAddress);
// Check if user has enough voting power
const hasEnoughStake = votingPower.gte(minProposalStake);
return {
hasEnoughStake,
votingPower: ethers.utils.formatEther(votingPower),
minProposalStake: ethers.utils.formatEther(minProposalStake),
};
}
Calculating Proposal Status
async function getProposalStatus(daoAddress, proposalId) {
const dao = new ethers.Contract(daoAddress, DAO_ABI, provider);
// Get proposal data
const proposal = await dao.getProposal(proposalId);
const [proposalType, forVotes, againstVotes, endTime, executed] = proposal;
// Get quorum
const quorum = await dao.quorum();
// Get total staked
const stakingAddress = await dao.upgradeableContracts(0); // 0 is the index for Staking
const staking = new ethers.Contract(stakingAddress, DAO_STAKING_ABI, provider);
const totalStaked = await staking.totalStaked();
// Calculate status
const now = Math.floor(Date.now() / 1000);
const votingEnded = now > endTime.toNumber();
// Calculate quorum percentage
const quorumPercentage = quorum.toNumber() / 10000; // Convert basis points to decimal
const quorumAmount = totalStaked.mul(quorum).div(10000);
// Calculate total votes
const totalVotes = forVotes.add(againstVotes);
// Check if quorum is reached
const quorumReached = totalVotes.gte(quorumAmount);
// Check if proposal passed
const passed = forVotes.gt(againstVotes);
// Determine status
let status;
if (executed) {
status = 'Executed';
} else if (!votingEnded) {
status = 'Active';
} else if (!quorumReached) {
status = 'Failed (Quorum not reached)';
} else if (!passed) {
status = 'Failed (Majority not reached)';
} else {
status = 'Ready for execution';
}
return {
status,
votingEnded,
quorumReached,
passed,
canExecute: votingEnded && quorumReached && passed && !executed,
stats: {
forVotes: ethers.utils.formatEther(forVotes),
againstVotes: ethers.utils.formatEther(againstVotes),
totalVotes: ethers.utils.formatEther(totalVotes),
quorumAmount: ethers.utils.formatEther(quorumAmount),
quorumPercentage: quorumPercentage * 100,
totalStaked: ethers.utils.formatEther(totalStaked),
votingEndTime: new Date(endTime.toNumber() * 1000),
}
};
}
Error Handling
async function safeContractCall(contractCall) {
try {
return await contractCall();
} catch (error) {
// Parse error message
let errorMessage = error.message;
// Check for revert reason
if (error.data) {
try {
// Try to parse error data
const errorData = error.data;
const decodedError = ethers.utils.toUtf8String(
'0x' + errorData.substring(138)
);
errorMessage = decodedError;
} catch (e) {
// If parsing fails, use original error message
console.error('Failed to parse error data:', e);
}
}
throw new Error(`Contract call failed: ${errorMessage}`);
}
}
// Example usage
async function safeVote(daoAddress, proposalId, support) {
const dao = new ethers.Contract(daoAddress, DAO_ABI, signer);
return safeContractCall(async () => {
const tx = await dao.vote(proposalId, support);
return tx.wait();
});
}
Gas Optimization
async function optimizedContractCall(contractMethod, args, gasMultiplier = 1.2) {
try {
// Estimate gas
const gasEstimate = await contractMethod.estimateGas(...args);
// Add buffer to gas estimate
const gasLimit = gasEstimate.mul(Math.floor(gasMultiplier * 100)).div(100);
// Execute transaction with gas limit
const tx = await contractMethod(...args, { gasLimit });
return tx.wait();
} catch (error) {
console.error('Transaction failed:', error);
throw error;
}
}
// Example usage
async function optimizedVote(daoAddress, proposalId, support) {
const dao = new ethers.Contract(daoAddress, DAO_ABI, signer);
return optimizedContractCall(dao.vote, [proposalId, support]);
}
Conclusion
This guide provides a foundation for programmatically interacting with CreateDAO contracts. You can use these patterns to build applications that create, manage, and interact with DAOs.
For more information on building a complete DAO management platform, refer to the Building a Management Platform guide.
On this page
- Contract Interaction
- Prerequisites
- Setting Up
- 1. Install Dependencies
- 2. Configure Provider
- 3. Load Contract ABIs
- Reading Contract Data
- Connecting to Contracts
- Reading Basic DAO Information
- Reading Proposal Data
- Reading Treasury Data
- Reading Staking Data
- Writing Transactions
- Setting Up a Signer
- Creating a DAO
- Creating a Proposal
- Voting on a Proposal
- Executing a Proposal
- Staking Tokens
- Unstaking Tokens
- Handling Events
- Listening for New Proposals
- Listening for Votes
- Listening for Proposal Execution
- Common Use Cases
- Fetching All DAOs Created by a Factory
- Fetching All Proposals for a DAO
- Checking if a User Can Create a Proposal
- Calculating Proposal Status
- Error Handling
- Gas Optimization
- Conclusion
Assistant
Responses are generated using AI and may contain mistakes.