Frontend Development¶
Guide to developing the React frontend for Prediction DAO.
Technology Stack¶
- React 18+ - UI framework
- Vite - Build tool and dev server
- ethers.js v6 - Ethereum library
- React Hooks - State management
- CSS - Styling
Project Structure¶
frontend/
├── src/
│ ├── components/
│ │ ├── ProposalSubmission.jsx
│ │ ├── ProposalList.jsx
│ │ ├── WelfareMetrics.jsx
│ │ └── MarketTrading.jsx
│ ├── App.jsx
│ ├── App.css
│ ├── main.jsx
│ └── config.js
├── public/
├── index.html
├── vite.config.js
└── package.json
Getting Started¶
Connecting to Contracts¶
Contract Configuration¶
// src/config.js
export const contracts = {
FutarchyGovernor: "0x...",
WelfareMetricRegistry: "0x...",
ProposalRegistry: "0x...",
ConditionalMarketFactory: "0x...",
PrivacyCoordinator: "0x...",
OracleResolver: "0x...",
RagequitModule: "0x..."
};
export const network = {
chainId: 1337,
name: "Hardhat Local"
};
Using ethers.js¶
import { ethers } from 'ethers';
import { contracts } from './config';
// Connect to provider
const provider = new ethers.BrowserProvider(window.ethereum);
// Get signer
const signer = await provider.getSigner();
// Create contract instance
const proposalRegistry = new ethers.Contract(
contracts.ProposalRegistry,
ProposalRegistryABI,
signer
);
// Call contract methods
const tx = await proposalRegistry.submitProposal(...);
await tx.wait();
Key Components¶
Wallet Connection¶
const [account, setAccount] = useState(null);
const [provider, setProvider] = useState(null);
const connectWallet = async () => {
if (window.ethereum) {
try {
const provider = new ethers.BrowserProvider(window.ethereum);
await provider.send("eth_requestAccounts", []);
const signer = await provider.getSigner();
const address = await signer.getAddress();
setProvider(provider);
setAccount(address);
} catch (error) {
console.error("Error connecting wallet:", error);
}
} else {
alert("Please install MetaMask!");
}
};
Proposal Submission¶
const ProposalSubmission = () => {
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const [fundingAmount, setFundingAmount] = useState('');
const submitProposal = async () => {
try {
const tx = await proposalRegistry.submitProposal(
title,
description,
ethers.parseEther(fundingAmount),
recipientAddress,
welfareMetricId,
{ value: ethers.parseEther("50") }
);
await tx.wait();
alert("Proposal submitted!");
} catch (error) {
console.error("Error submitting proposal:", error);
}
};
return (
<form onSubmit={submitProposal}>
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Proposal Title"
/>
{/* More form fields */}
<button type="submit">Submit Proposal</button>
</form>
);
};
Reading Contract State¶
const ProposalList = () => {
const [proposals, setProposals] = useState([]);
useEffect(() => {
const loadProposals = async () => {
const count = await proposalRegistry.proposalCount();
const proposalArray = [];
for (let i = 0; i < count; i++) {
const proposal = await proposalRegistry.getProposal(i);
proposalArray.push(proposal);
}
setProposals(proposalArray);
};
loadProposals();
}, []);
return (
<div>
{proposals.map((proposal, index) => (
<div key={index}>
<h3>{proposal.title}</h3>
<p>{proposal.description}</p>
</div>
))}
</div>
);
};
Listening to Events¶
useEffect(() => {
const filter = proposalRegistry.filters.ProposalSubmitted();
const handleProposalSubmitted = (proposalId, proposer) => {
console.log(`New proposal ${proposalId} from ${proposer}`);
// Update UI
};
proposalRegistry.on(filter, handleProposalSubmitted);
return () => {
proposalRegistry.off(filter, handleProposalSubmitted);
};
}, [proposalRegistry]);
Best Practices¶
Error Handling¶
try {
const tx = await contract.method();
await tx.wait();
} catch (error) {
if (error.code === 'ACTION_REJECTED') {
alert("Transaction rejected by user");
} else if (error.code === 'INSUFFICIENT_FUNDS') {
alert("Insufficient funds");
} else {
console.error("Transaction error:", error);
alert("Transaction failed. See console for details.");
}
}
Loading States¶
const [loading, setLoading] = useState(false);
const submitTransaction = async () => {
setLoading(true);
try {
const tx = await contract.method();
await tx.wait();
} finally {
setLoading(false);
}
};
return (
<button disabled={loading} onClick={submitTransaction}>
{loading ? "Processing..." : "Submit"}
</button>
);
Network Detection¶
useEffect(() => {
const checkNetwork = async () => {
const { chainId } = await provider.getNetwork();
if (chainId !== expectedChainId) {
alert("Please switch to the correct network");
}
};
if (provider) {
checkNetwork();
}
}, [provider]);
Building for Production¶
Output will be in dist/ directory.