mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-23 12:47:55 +00:00
Integrate live blockchain data for governance features
Added: - useGovernance hook: Fetches live proposals and referenda from blockchain - useTreasury hook: Fetches live treasury balance and proposals - TreasuryOverview: Now uses real blockchain data with loading/error states - Forum database schema: Admin announcements, categories, discussions, replies, reactions Features: - Live data badge shows active blockchain connection - Automatic refresh every 30 seconds for treasury data - Secure RLS policies for forum access control - Admin announcements system with priority and expiry - Forum reactions (upvote/downvote) support Next: Complete forum UI with admin banner and moderation panel
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { usePolkadot } from '@/contexts/PolkadotContext';
|
||||
|
||||
export interface Proposal {
|
||||
id: string;
|
||||
proposalIndex: number;
|
||||
hash: string;
|
||||
proposer: string;
|
||||
value: string;
|
||||
beneficiary: string;
|
||||
bond: string;
|
||||
status: 'active' | 'approved' | 'rejected';
|
||||
method: string;
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
export interface Referendum {
|
||||
id: string;
|
||||
index: number;
|
||||
hash: string;
|
||||
threshold: string;
|
||||
delay: number;
|
||||
end: number;
|
||||
voteCount: number;
|
||||
ayeVotes: string;
|
||||
nayVotes: string;
|
||||
status: 'ongoing' | 'passed' | 'failed';
|
||||
}
|
||||
|
||||
export function useGovernance() {
|
||||
const { api, isConnected } = usePolkadot();
|
||||
const [proposals, setProposals] = useState<Proposal[]>([]);
|
||||
const [referenda, setReferenda] = useState<Referendum[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!api || !isConnected) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchGovernanceData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// Fetch Treasury Proposals
|
||||
const proposalsData = await api.query.treasury?.proposals?.entries();
|
||||
if (proposalsData) {
|
||||
const parsedProposals: Proposal[] = proposalsData.map(([key, value]: any) => {
|
||||
const proposalIndex = key.args[0].toNumber();
|
||||
const proposal = value.unwrap();
|
||||
|
||||
return {
|
||||
id: `prop-${proposalIndex}`,
|
||||
proposalIndex,
|
||||
hash: key.toHex(),
|
||||
proposer: proposal.proposer.toString(),
|
||||
value: proposal.value.toString(),
|
||||
beneficiary: proposal.beneficiary.toString(),
|
||||
bond: proposal.bond.toString(),
|
||||
status: 'active',
|
||||
method: 'treasury.approveProposal',
|
||||
createdAt: Date.now()
|
||||
};
|
||||
});
|
||||
setProposals(parsedProposals);
|
||||
}
|
||||
|
||||
// Fetch Democracy Referenda
|
||||
const referendaData = await api.query.democracy?.referendumInfoOf?.entries();
|
||||
if (referendaData) {
|
||||
const parsedReferenda: Referendum[] = referendaData.map(([key, value]: any) => {
|
||||
const index = key.args[0].toNumber();
|
||||
const info = value.unwrap();
|
||||
|
||||
if (info.isOngoing) {
|
||||
const ongoing = info.asOngoing;
|
||||
return {
|
||||
id: `ref-${index}`,
|
||||
index,
|
||||
hash: key.toHex(),
|
||||
threshold: ongoing.threshold.toString(),
|
||||
delay: ongoing.delay.toNumber(),
|
||||
end: ongoing.end.toNumber(),
|
||||
voteCount: 0,
|
||||
ayeVotes: ongoing.tally?.ayes?.toString() || '0',
|
||||
nayVotes: ongoing.tally?.nays?.toString() || '0',
|
||||
status: 'ongoing' as const
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}).filter(Boolean) as Referendum[];
|
||||
|
||||
setReferenda(parsedReferenda);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error('Error fetching governance data:', err);
|
||||
setError(err instanceof Error ? err.message : 'Failed to fetch governance data');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchGovernanceData();
|
||||
|
||||
// Subscribe to updates
|
||||
const interval = setInterval(fetchGovernanceData, 30000); // Refresh every 30 seconds
|
||||
return () => clearInterval(interval);
|
||||
}, [api, isConnected]);
|
||||
|
||||
return {
|
||||
proposals,
|
||||
referenda,
|
||||
loading,
|
||||
error
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { usePolkadot } from '@/contexts/PolkadotContext';
|
||||
|
||||
export interface TreasuryMetrics {
|
||||
totalBalance: number;
|
||||
monthlyIncome: number;
|
||||
monthlyExpenses: number;
|
||||
pendingProposals: number;
|
||||
approvedBudget: number;
|
||||
healthScore: number;
|
||||
}
|
||||
|
||||
export interface TreasuryProposal {
|
||||
id: string;
|
||||
index: number;
|
||||
proposer: string;
|
||||
beneficiary: string;
|
||||
value: string;
|
||||
bond: string;
|
||||
status: 'pending' | 'approved' | 'rejected';
|
||||
}
|
||||
|
||||
export function useTreasury() {
|
||||
const { api, isConnected } = usePolkadot();
|
||||
const [metrics, setMetrics] = useState<TreasuryMetrics>({
|
||||
totalBalance: 0,
|
||||
monthlyIncome: 0,
|
||||
monthlyExpenses: 0,
|
||||
pendingProposals: 0,
|
||||
approvedBudget: 0,
|
||||
healthScore: 0
|
||||
});
|
||||
const [proposals, setProposals] = useState<TreasuryProposal[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!api || !isConnected) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchTreasuryData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// Get treasury account balance
|
||||
const treasuryAccount = await api.query.treasury?.treasury?.();
|
||||
let totalBalance = 0;
|
||||
|
||||
if (treasuryAccount) {
|
||||
totalBalance = parseInt(treasuryAccount.toString()) / 1e12; // Convert from planck to tokens
|
||||
}
|
||||
|
||||
// Fetch all treasury proposals
|
||||
const proposalsData = await api.query.treasury?.proposals?.entries();
|
||||
const proposalsList: TreasuryProposal[] = [];
|
||||
let approvedBudget = 0;
|
||||
let pendingCount = 0;
|
||||
|
||||
if (proposalsData) {
|
||||
proposalsData.forEach(([key, value]: any) => {
|
||||
const index = key.args[0].toNumber();
|
||||
const proposal = value.unwrap();
|
||||
const valueAmount = parseInt(proposal.value.toString()) / 1e12;
|
||||
|
||||
const proposalItem: TreasuryProposal = {
|
||||
id: `treasury-${index}`,
|
||||
index,
|
||||
proposer: proposal.proposer.toString(),
|
||||
beneficiary: proposal.beneficiary.toString(),
|
||||
value: proposal.value.toString(),
|
||||
bond: proposal.bond.toString(),
|
||||
status: 'pending'
|
||||
};
|
||||
|
||||
proposalsList.push(proposalItem);
|
||||
pendingCount++;
|
||||
approvedBudget += valueAmount;
|
||||
});
|
||||
}
|
||||
|
||||
// Calculate health score (simplified)
|
||||
const healthScore = Math.min(100, Math.round((totalBalance / (approvedBudget || 1)) * 100));
|
||||
|
||||
setMetrics({
|
||||
totalBalance,
|
||||
monthlyIncome: 0, // This would require historical data
|
||||
monthlyExpenses: 0, // This would require historical data
|
||||
pendingProposals: pendingCount,
|
||||
approvedBudget,
|
||||
healthScore: isNaN(healthScore) ? 0 : healthScore
|
||||
});
|
||||
|
||||
setProposals(proposalsList);
|
||||
|
||||
} catch (err) {
|
||||
console.error('Error fetching treasury data:', err);
|
||||
setError(err instanceof Error ? err.message : 'Failed to fetch treasury data');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchTreasuryData();
|
||||
|
||||
// Subscribe to updates
|
||||
const interval = setInterval(fetchTreasuryData, 30000); // Refresh every 30 seconds
|
||||
return () => clearInterval(interval);
|
||||
}, [api, isConnected]);
|
||||
|
||||
return {
|
||||
metrics,
|
||||
proposals,
|
||||
loading,
|
||||
error
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user