Creating DAO Proposals
Learn how to create, vote on, and execute governance proposals in the YaniPay DAO.
Introduction
The YaniPay DAO (Decentralized Autonomous Organization) gives YANI token holders the power to shape the protocol's future. This guide covers everything you need to know about participating in governance.
What you'll learn
- How the YaniPay governance system works
- Calculating and increasing voting power
- Creating well-structured proposals
- The proposal lifecycle from creation to execution
Governance Overview
YaniPay uses a token-weighted governance model where YANI holders can vote on protocol changes, treasury allocations, and ecosystem upgrades.
1M YANI
Min. to create proposal
7 Days
Voting period
4% Quorum
Of total supply
Proposal Types
- Protocol Parameters - Fee adjustments, rate limits, etc.
- Treasury Allocations - Funding grants, partnerships, development
- Ecosystem Upgrades - New features, integrations, migrations
- Emergency Actions - Security patches, circuit breakers
Voting Power
Your voting power is determined by your YANI holdings and staking status. Here's how to check and manage your voting power:
1 interface VotingPower { 2 address: string; 3 yaniBalance: string; 4 stakedYani: string; 5 delegatedPower: string; 6 totalPower: string; 7 votingPowerPercent: string; 8 } 9 10 // Get current voting power 11 export async function getVotingPower(): Promise<VotingPower> { 12 const response = await fetch('/api/defi/governance/voting-power', { 13 credentials: 'include', 14 }); 15 16 if (!response.ok) throw new Error('Failed to fetch voting power'); 17 return response.json(); 18 } 19 20 // Delegate voting power to another address 21 export async function delegateVotingPower( 22 delegatee: string 23 ): Promise<{ transactionHash: string }> { 24 const response = await fetch('/api/defi/governance/delegate', { 25 method: 'POST', 26 headers: { 'Content-Type': 'application/json' }, 27 credentials: 'include', 28 body: JSON.stringify({ delegatee }), 29 }); 30 31 if (!response.ok) throw new Error('Failed to delegate'); 32 return response.json(); 33 } 34 35 // Revoke delegation 36 export async function revokeDelegation(): Promise<{ transactionHash: string }> { 37 const response = await fetch('/api/defi/governance/revoke-delegation', { 38 method: 'POST', 39 credentials: 'include', 40 }); 41 42 if (!response.ok) throw new Error('Failed to revoke delegation'); 43 return response.json(); 44 }
Voting Power Multipliers
- Wallet YANI: 1x voting power
- Staked YANI (30 days): 1.25x voting power
- Staked YANI (90 days): 1.5x voting power
- Staked YANI (365 days): 2x voting power
Creating Proposals
To create a proposal, you need at least 1,000,000 YANI in voting power. Here's the complete process:
1 interface ProposalAction { 2 target: string; // Contract address to call 3 value: string; // ETH value to send 4 signature: string; // Function signature 5 calldata: string; // Encoded function arguments 6 } 7 8 interface CreateProposalInput { 9 title: string; 10 description: string; 11 category: 'protocol' | 'treasury' | 'ecosystem' | 'emergency'; 12 actions: ProposalAction[]; 13 discussionUrl?: string; 14 } 15 16 interface Proposal { 17 id: string; 18 proposer: string; 19 title: string; 20 description: string; 21 category: string; 22 status: 'pending' | 'active' | 'defeated' | 'succeeded' | 'queued' | 'executed' | 'cancelled'; 23 forVotes: string; 24 againstVotes: string; 25 abstainVotes: string; 26 startTime: string; 27 endTime: string; 28 actions: ProposalAction[]; 29 } 30 31 // Create a new proposal 32 export async function createProposal( 33 input: CreateProposalInput 34 ): Promise<Proposal> { 35 const response = await fetch('/api/defi/governance/proposals', { 36 method: 'POST', 37 headers: { 'Content-Type': 'application/json' }, 38 credentials: 'include', 39 body: JSON.stringify(input), 40 }); 41 42 if (!response.ok) { 43 const error = await response.json(); 44 throw new Error(error.message || 'Failed to create proposal'); 45 } 46 47 return response.json(); 48 } 49 50 // Get all proposals 51 export async function getProposals( 52 status?: string, 53 page: number = 1 54 ): Promise<{ proposals: Proposal[]; totalCount: number }> { 55 const params = new URLSearchParams({ page: page.toString() }); 56 if (status) params.set('status', status); 57 58 const response = await fetch(`/api/defi/governance/proposals?${params}`); 59 if (!response.ok) throw new Error('Failed to fetch proposals'); 60 return response.json(); 61 } 62 63 // Get a specific proposal 64 export async function getProposal(id: string): Promise<Proposal> { 65 const response = await fetch(`/api/defi/governance/proposals/${id}`); 66 if (!response.ok) throw new Error('Failed to fetch proposal'); 67 return response.json(); 68 }
Example: Creating a Treasury Proposal
1 'use client'; 2 3 import { useState } from 'react'; 4 import { createProposal } from '@/lib/api/proposals'; 5 import { Loader2 } from 'lucide-react'; 6 7 export function CreateProposalForm() { 8 const [title, setTitle] = useState(''); 9 const [description, setDescription] = useState(''); 10 const [category, setCategory] = useState<'protocol' | 'treasury' | 'ecosystem'>('treasury'); 11 const [isSubmitting, setIsSubmitting] = useState(false); 12 13 const handleSubmit = async (e: React.FormEvent) => { 14 e.preventDefault(); 15 setIsSubmitting(true); 16 17 try { 18 // Example: Treasury allocation for ecosystem grants 19 const proposal = await createProposal({ 20 title, 21 description, 22 category, 23 actions: [ 24 { 25 target: '0x...Treasury', // Treasury contract address 26 value: '0', 27 signature: 'transfer(address,uint256)', 28 calldata: '0x...', // Encoded recipient and amount 29 }, 30 ], 31 discussionUrl: 'https://forum.yanipay.com/proposal/123', 32 }); 33 34 alert(`Proposal created! ID: ${proposal.id}`); 35 } catch (err) { 36 alert('Failed to create proposal. Check your voting power.'); 37 } finally { 38 setIsSubmitting(false); 39 } 40 }; 41 42 return ( 43 <form onSubmit={handleSubmit} className="flex flex-col gap-6 max-w-2xl"> 44 <div> 45 <label className="block text-sm font-medium mb-2">Title</label> 46 <input 47 type="text" 48 value={title} 49 onChange={(e) => setTitle(e.target.value)} 50 placeholder="[YIP-XX] Short descriptive title" 51 className="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900" 52 required 53 /> 54 </div> 55 56 <div> 57 <label className="block text-sm font-medium mb-2">Category</label> 58 <select 59 value={category} 60 onChange={(e) => setCategory(e.target.value as any)} 61 className="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900" 62 > 63 <option value="protocol">Protocol Parameters</option> 64 <option value="treasury">Treasury Allocation</option> 65 <option value="ecosystem">Ecosystem Upgrade</option> 66 </select> 67 </div> 68 69 <div> 70 <label className="block text-sm font-medium mb-2">Description (Markdown)</label> 71 <textarea 72 value={description} 73 onChange={(e) => setDescription(e.target.value)} 74 placeholder="## Summary\n\n## Motivation\n\n## Specification\n\n## Timeline" 75 rows={12} 76 className="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 font-mono text-sm" 77 required 78 /> 79 </div> 80 81 <button 82 type="submit" 83 disabled={isSubmitting} 84 className="w-full py-3 rounded-lg bg-orange-600 hover:bg-orange-700 disabled:bg-gray-400 text-white font-medium" 85 > 86 {isSubmitting ? ( 87 <span className="flex items-center justify-center gap-2"> 88 <Loader2 className="w-5 h-5 animate-spin" /> 89 Creating Proposal... 90 </span> 91 ) : ( 92 'Create Proposal' 93 )} 94 </button> 95 </form> 96 ); 97 }
Voting on Proposals
Once a proposal is active, YANI holders can cast their votes. Here's how to participate:
1 type VoteType = 'for' | 'against' | 'abstain'; 2 3 interface Vote { 4 proposalId: string; 5 voter: string; 6 voteType: VoteType; 7 votingPower: string; 8 reason?: string; 9 timestamp: string; 10 } 11 12 // Cast a vote 13 export async function castVote( 14 proposalId: string, 15 voteType: VoteType, 16 reason?: string 17 ): Promise<Vote> { 18 const response = await fetch(`/api/defi/governance/proposals/${proposalId}/vote`, { 19 method: 'POST', 20 headers: { 'Content-Type': 'application/json' }, 21 credentials: 'include', 22 body: JSON.stringify({ voteType, reason }), 23 }); 24 25 if (!response.ok) throw new Error('Failed to cast vote'); 26 return response.json(); 27 } 28 29 // Get votes for a proposal 30 export async function getProposalVotes( 31 proposalId: string, 32 page: number = 1 33 ): Promise<{ votes: Vote[]; totalCount: number }> { 34 const response = await fetch( 35 `/api/defi/governance/proposals/${proposalId}/votes?page=${page}` 36 ); 37 if (!response.ok) throw new Error('Failed to fetch votes'); 38 return response.json(); 39 } 40 41 // Check if user has voted 42 export async function hasVoted(proposalId: string): Promise<Vote | null> { 43 const response = await fetch( 44 `/api/defi/governance/proposals/${proposalId}/my-vote`, 45 { credentials: 'include' } 46 ); 47 48 if (response.status === 404) return null; 49 if (!response.ok) throw new Error('Failed to check vote'); 50 return response.json(); 51 }
Proposal Execution
After a proposal succeeds, it enters a 2-day timelock before execution:
1 // Queue a succeeded proposal for execution 2 export async function queueProposal( 3 proposalId: string 4 ): Promise<{ transactionHash: string; eta: string }> { 5 const response = await fetch( 6 `/api/defi/governance/proposals/${proposalId}/queue`, 7 { 8 method: 'POST', 9 credentials: 'include', 10 } 11 ); 12 13 if (!response.ok) throw new Error('Failed to queue proposal'); 14 return response.json(); 15 } 16 17 // Execute a queued proposal after timelock 18 export async function executeProposal( 19 proposalId: string 20 ): Promise<{ transactionHash: string }> { 21 const response = await fetch( 22 `/api/defi/governance/proposals/${proposalId}/execute`, 23 { 24 method: 'POST', 25 credentials: 'include', 26 } 27 ); 28 29 if (!response.ok) throw new Error('Failed to execute proposal'); 30 return response.json(); 31 } 32 33 // Cancel a proposal (only proposer or guardian) 34 export async function cancelProposal( 35 proposalId: string, 36 reason: string 37 ): Promise<{ transactionHash: string }> { 38 const response = await fetch( 39 `/api/defi/governance/proposals/${proposalId}/cancel`, 40 { 41 method: 'POST', 42 headers: { 'Content-Type': 'application/json' }, 43 credentials: 'include', 44 body: JSON.stringify({ reason }), 45 } 46 ); 47 48 if (!response.ok) throw new Error('Failed to cancel proposal'); 49 return response.json(); 50 }
Proposal Lifecycle
Best Practices
Start with discussion
Post on the governance forum before creating on-chain proposals to gather feedback.
Be specific and detailed
Include clear motivation, technical specifications, and implementation timeline.
Consider all stakeholders
Address how your proposal impacts different user groups in the ecosystem.
Test on testnet first
For protocol changes, always test the execution on YaniChain testnet before mainnet.
Next Steps
Ready to participate in YaniPay governance? Check out these resources: