Guide to programmatically interacting with CreateDAO contracts
# Using npm
npm install ethers
# Using yarn
yarn add ethers
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');
// 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
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);
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
}
};
}
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,
};
}
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),
};
}
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'),
};
}
// 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);
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');
}
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');
}
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,
};
}
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,
};
}
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,
};
}
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,
};
}
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');
};
}
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');
};
}
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');
};
}
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;
}
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;
}
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),
};
}
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),
}
};
}
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();
});
}
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]);
}