Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
LABS
Guides

Setting Up a Transparent Grant Proposal Portal

This guide provides a technical walkthrough for developers to build a decentralized application for submitting, reviewing, and tracking research grant proposals with full on-chain transparency.
Chainscore © 2026
introduction
ON-CHAIN GOVERNANCE

Setting Up a Transparent Grant Proposal Portal

A technical guide to building a decentralized, transparent grant funding system using smart contracts and IPFS.

On-chain grant portals are decentralized applications (dApps) that manage the entire grant lifecycle—from proposal submission and community voting to fund distribution and progress tracking—on a blockchain. Unlike traditional, opaque grant systems, these portals use smart contracts to enforce rules transparently and decentralized storage like IPFS or Arweave to host proposal documents immutably. This architecture ensures that all decisions, votes, and fund flows are publicly verifiable, reducing administrative overhead and building trust within a DAO or developer community. Key components include a proposal factory contract, a treasury module, and a voting mechanism, often integrated with token-based governance.

The core of the portal is its smart contract architecture. A typical setup involves a GrantFactory contract that allows users to submit new proposals by creating instances of a GrantProposal contract. Each proposal contract stores its metadata hash (pointing to IPFS), requested funding amount, recipient address, and voting state. A separate Treasury contract, often governed by a multisig wallet or a governance token vote, holds the funds and executes disbursements. For voting, you can integrate existing solutions like OpenZeppelin's Governor or build a custom snapshot mechanism using ERC-20 or ERC-721 tokens. All contract interactions, from submission to final payout, are recorded on-chain.

To ensure proposals are human-readable and rich in content, their detailed descriptions, milestones, and budgets are stored off-chain in a decentralized manner. The standard practice is to create a JSON metadata file following a schema (like the one used by OpenGrants) and upload it to IPFS using a service like Pinata or web3.storage. The returned Content Identifier (CID) is then stored on-chain within the proposal contract. This pattern, often called "data availability," keeps heavy data off the expensive blockchain while guaranteeing its permanence and accessibility. The frontend fetches this CID to display the full proposal to voters.

Building the frontend involves connecting the smart contracts to a user interface. Using a framework like Next.js with wagmi and RainbowKit for wallet connection is common. The dApp needs to listen for ProposalCreated events from the factory, fetch proposal data from both the blockchain (state, votes) and IPFS (description), and allow token holders to cast votes by calling the contract's vote function. It's crucial to display clear voting deadlines, funding status, and a history of transactions. For a production-ready example, review the frontend code of MolochDAO's v2 or Gitcoin Grants' earlier rounds, which popularized quadratic funding on-chain.

Security and transparency audits are non-negotiable before launch. All smart contracts should undergo a professional audit by firms like ConsenSys Diligence or OpenZeppelin. Key risks include reentrancy in treasury payouts, vote manipulation, and metadata tampering. Furthermore, the governance process itself must be designed to resist attacks; consider implementing a timelock on treasury transactions and a proposal quorum. Transparency is achieved by making the entire codebase open-source, publishing audit reports, and using block explorers like Etherscan to verify contract activity. This open verification is what distinguishes a true on-chain portal from a web2 system with a blockchain facade.

Successful deployment requires careful planning of the launch sequence: 1) Deploy and verify core contracts (Treasury, Factory, Voting), 2) Seed the treasury with funds, 3) Launch the frontend dApp, and 4) Announce the portal to your community with clear documentation. Post-launch, monitor proposal activity and be prepared to iterate on parameters like proposal submission fees or voting periods based on community feedback. The end goal is a self-sustaining, transparent ecosystem where projects can seek funding without intermediaries, governed entirely by code and community consensus.

prerequisites
GETTING STARTED

Prerequisites and Tech Stack

Before building a transparent grant proposal portal, you need the right tools and foundational knowledge. This guide covers the essential software, libraries, and concepts required to develop a secure, on-chain application for managing grants.

The core of a transparent grant portal is a smart contract deployed on a blockchain like Ethereum, Arbitrum, or Optimism. You will need a development environment to write, test, and deploy this contract. The most common stack includes Hardhat or Foundry for development and testing, Node.js (v18+), and a package manager like npm or yarn. You'll also need a wallet (e.g., MetaMask) for deployment and interaction, and testnet ETH/ERC-20 tokens for funding proposals during development.

Your smart contract will handle the grant lifecycle: proposal submission, voting, funding, and milestone tracking. Key libraries to integrate are OpenZeppelin Contracts for secure, audited base contracts like Ownable, ReentrancyGuard, and token standards (ERC20, ERC721). For on-chain governance, consider using OpenZeppelin's Governor contracts. You will write the contract logic in Solidity (v0.8.x), ensuring it includes events for all state changes to enable off-chain indexing and transparency.

To create a functional full-stack dApp, you'll need a frontend framework. Next.js (with TypeScript) is a popular choice for its server-side rendering and API route capabilities. You will use a Web3 library to connect the frontend to your smart contract and the user's wallet. Wagmi and viem are modern, type-safe alternatives to older libraries like web3.js or ethers.js. They simplify reading contract data, sending transactions, and listening for events.

A transparent portal requires an accessible record of all on-chain activity. You must index blockchain events emitted by your contract. You can use The Graph to create a subgraph that indexes proposal creation, votes, and fund transfers. Alternatively, for simpler projects, you can query event logs directly using viem or an RPC provider's API. You will also need a way to store proposal details and supporting documents, typically using decentralized storage like IPFS via Pinata or web3.storage.

Finally, you need a plan for testing and deployment. Write comprehensive unit and integration tests using Hardhat's testing environment or Foundry's Forge. Use a scripting language like JavaScript/TypeScript or Solidity (for Foundry) to automate deployment. You will need access to an RPC node; services like Alchemy or Infura provide reliable connections. For the final step, verify your contract's source code on block explorers like Etherscan to complete the transparency loop for users and auditors.

smart-contract-design
CORE ARCHITECTURE

Step 1: Designing the Smart Contract

The foundation of a transparent grant portal is a secure, auditable smart contract. This step defines the data structures, governance logic, and lifecycle rules for proposals.

Begin by defining the core data structures in Solidity. A Proposal struct typically includes fields like id, applicant, title, description, amountRequested, amountGranted, status, and timestamps for creation, voting, and completion. Use enum ProposalStatus { Pending, Active, Approved, Rejected, Completed } to manage state. Store proposals in a mapping, such as mapping(uint256 => Proposal) public proposals, and maintain a counter for IDs. Consider implementing IPFS or Arweave hashes in the description field to link to detailed documentation off-chain, keeping gas costs manageable.

Next, implement the governance logic for proposal submission and voting. The submitProposal function should allow users to create new entries, often requiring a staked deposit to prevent spam. For voting, you can implement a simple token-weighted model using an ERC-20 or ERC-721 (for NFT-based DAOs) snapshot, or a more complex quadratic voting mechanism. Critical functions must include access control modifiers, typically using OpenZeppelin's Ownable or AccessControl libraries to restrict actions like moving a proposal to a vote or disbursing funds to designated onlyOwner or onlyGovernance roles.

Finally, design the proposal lifecycle and fund management. After a voting period ends, an executeProposal function should check if the proposal met quorum and passed the vote threshold before updating its status and transferring funds via address.send() or address.transfer(). It's essential to include a timelock mechanism for approved proposals, adding a delay between approval and execution to allow for community review. For transparency, emit detailed events like ProposalSubmitted, VoteCast, and ProposalExecuted for all state changes, enabling easy off-chain tracking by frontends and indexers.

implementing-contract-logic
BUILDING THE PORTAL

Step 2: Implementing Core Contract Functions

This section details the core smart contract logic for creating, funding, and managing grant proposals in a transparent, on-chain system.

The heart of a transparent grant portal is its smart contract. We'll implement three primary functions: createProposal, fundProposal, and executeProposal. Start by defining the Proposal struct to store all relevant data. This should include the proposer address, a description string, the requested amount in the native token (e.g., ETH), the current amountFunded, a deadline timestamp, and a boolean executed flag to prevent re-entrancy attacks. Store proposals in a public mapping, such as mapping(uint256 => Proposal) public proposals;.

The createProposal function allows users to submit new grant requests. It should accept parameters for description and amount, then mint a new Proposal struct with a unique ID. Critical security checks include verifying the amount is greater than zero and setting a reasonable deadline (e.g., block.timestamp + 30 days). Emit an event like ProposalCreated with all proposal details to allow off-chain indexers and frontends to track submissions transparently. This event-driven architecture is a best practice for dApp development.

Next, implement fundProposal to allow contributors to donate to a specific grant. This function will be payable and update the proposal's amountFunded state variable. You must include guards to check that the proposal exists, is not past its deadline, and has not yet been fully funded. Use require(block.timestamp < proposals[proposalId].deadline, "Funding period ended");. Always use the Checks-Effects-Interactions pattern: validate inputs, update state, and then transfer funds using address(this).balance or a specific ERC-20 token contract.

The final core function is executeProposal, which releases the funds to the proposer once the funding goal is met. This function should verify that amountFunded >= amount, the deadline has passed, and executed is false. After these checks, mark the proposal as executed = true before transferring the funds to mitigate re-entrancy risks. Use proposals[proposalId].proposer.call{value: amountFunded}("") for the native token transfer. For added robustness, consider implementing a withdrawal pattern where the proposer pulls the funds, which can simplify security and gas management.

To enhance transparency, add view functions like getProposalDetails that return the full struct for a given ID. Consider integrating with a price oracle if funding goals are in USD, or implementing a quadratic funding mechanism for more democratic distribution. All contract code should be thoroughly tested using frameworks like Foundry or Hardhat before deployment to a testnet. The complete, audited code for a basic version of this contract can be found in the OpenZeppelin Contracts Wizard under the 'Governance' examples.

integrating-ipfs-storage
DECENTRALIZED FILE SYSTEM

Step 3: Integrating IPFS for Document Storage

This step details how to store grant proposal documents immutably and transparently on the InterPlanetary File System (IPFS).

IPFS provides a decentralized, content-addressed storage layer for your grant portal. Unlike traditional servers where files are located by a server address (like https://server.com/file.pdf), IPFS locates files by their cryptographic hash (CID). This means the document is immutable—any change creates a completely new CID. To integrate, you'll use a client library like ipfs-http-client to programmatically add files from your backend. The returned CID is the permanent, verifiable reference you will store on-chain or in your application's database.

For production, you need a reliable pinning service to ensure your files remain accessible. When you add a file to IPFS, it's only stored on your local node. Pinning services like Pinata, web3.storage, or Filecoin provide redundant, long-term storage. Their APIs allow you to pin files using the CID. A common pattern is to upload a proposal's supporting PDF to IPFS via a service's API, receive the CID, and then emit an on-chain event (like ProposalSubmitted) that includes this CID as a parameter, creating a permanent audit trail.

Here is a Node.js example using the ipfs-http-client library to add a document and pin it with Pinata. First, install the required packages: npm install ipfs-http-client axios. The code snippet below demonstrates the upload process, returning the CID for your application to use.

javascript
import { create } from 'ipfs-http-client';
import axios from 'axios';

// Configure clients
const ipfs = create({ host: 'ipfs.infura.io', port: 5001, protocol: 'https' });
const pinataJWT = 'YOUR_PINATA_JWT';

async function pinProposalDocument(fileBuffer) {
  // Add to IPFS
  const { cid } = await ipfs.add(fileBuffer);
  const cidString = cid.toString();
  
  // Pin via Pinata API for persistence
  await axios.post(
    `https://api.pinata.cloud/pinning/pinByHash`,
    { hashToPin: cidString },
    { headers: { Authorization: `Bearer ${pinataJWT}` } }
  );
  
  console.log(`Document pinned with CID: ${cidString}`);
  return cidString;
}

The returned CID (e.g., QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco) is your key to retrieving the document. Users can fetch it through a public IPFS gateway (like https://ipfs.io/ipfs/<CID>) or a dedicated gateway from your pinning service. For a seamless user experience, your frontend should integrate a gateway URL. This approach guarantees that the proposal document submitted during the grant application is the exact same one reviewers and the public can verify later, enforcing transparency.

Consider data privacy and cost. While IPFS data is public by nature, you can encrypt sensitive documents client-side before uploading, storing only the encrypted payload on IPFS. Be mindful of pinning service pricing models, which typically charge based on storage per GB per month. For a grant portal, this is usually minimal cost. This integration completes the core transparency stack: on-chain proposal metadata points to immutable, publicly verifiable documents stored on IPFS.

building-react-frontend
IMPLEMENTATION

Step 4: Building the React Frontend

This section details the development of a React-based user interface to interact with the on-chain grant program, focusing on connecting wallets, reading proposals, and submitting votes.

The frontend serves as the primary interface for governance participants. We'll build it using React with TypeScript for type safety, Vite for fast development tooling, and wagmi + viem for seamless Ethereum interaction. The core user flows are: connecting a wallet, fetching and displaying active proposals, and submitting votes or creating new proposals. We'll structure the application with a main dashboard component, a detailed proposal view, and a form for proposal creation.

Wallet connection is the first critical step. We'll use the wagmi library, which provides React hooks for Ethereum. Configure it with the createConfig function, specifying our Sepolia testnet RPC URL and wallet connectors like MetaMask and WalletConnect. The useAccount and useConnect hooks will manage connection state and trigger the wallet modal. Always display the user's truncated address and network after a successful connection to provide clear feedback.

To display proposals, we need to read data from the smart contract. Using wagmi's useReadContract hook, we can call the getAllProposals function we wrote earlier. This hook requires the contract ABI, address, and function name. We'll map the returned array to render a list of proposal cards, showing the title, description, current votes, and status. For real-time updates, we can use the useBlockNumber hook to refetch data when a new block is mined.

The voting functionality requires a write transaction. We'll use the useWriteContract hook along with useWaitForTransactionReceipt to handle the transaction lifecycle. Create a button that, when clicked, calls the contract's voteOnProposal function with the proposal ID and the user's choice (true for yes, false for no). It's essential to disable the button and show a pending state while the transaction is being confirmed to prevent duplicate submissions and improve UX.

For creating a proposal, build a form with fields for title, description, amount, and recipient address. On submission, the frontend will call the createProposal function. Since this function is only callable by the CHAIRPERSON role, the UI should conditionally show or hide the creation form based on the connected address, which can be checked against the contract's chairperson public variable using a useReadContract hook.

Finally, ensure a good user experience by adding clear loading states, transaction feedback (success/error toasts), and basic input validation. The complete code for this frontend, including component structure and styling with Tailwind CSS, is available in the Chainscore Labs GitHub repository. This portal now provides a full-stack, transparent interface for your on-chain grant program.

connecting-frontend-contract
BUILDING THE PORTAL

Connecting Frontend to Contract and IPFS

This step integrates the React frontend with the deployed smart contract and IPFS to create a functional, transparent grant proposal portal.

With the smart contract deployed and the IPFS uploader ready, the next step is to connect your React frontend. This involves using Ethers.js to interact with the blockchain and the Pinata SDK to handle file uploads. First, install the required dependencies: npm install ethers @pinata/sdk. You'll need your contract's ABI and address from the deployment step, and API keys from Pinata for IPFS access. Store these sensitive keys in environment variables (e.g., .env.local) to keep them secure and out of your version control.

Create a context provider or a dedicated service file to manage the application's state and blockchain interactions. Initialize an Ethers Provider (e.g., using JsonRpcProvider for a local Hardhat node or AlchemyProvider for a testnet) and a Signer from the user's connected wallet, typically via MetaMask using window.ethereum. Instantiate your contract using new ethers.Contract(contractAddress, contractABI, signer). This contract instance will be used to call functions like submitProposal and getProposals.

For the proposal submission form, handle the user's input and file attachment. When a user submits a form, your frontend logic should first upload the proposal PDF to IPFS using the Pinata client. The pinFileToIPFS method returns a Content Identifier (CID)—a unique hash that points to the file on IPFS. This CID, along with other proposal details like title, description, and requested amount, is then sent as parameters to your smart contract's submitProposal function via a transaction.

To display existing proposals, create a function that calls the contract's view function, getProposals. This returns an array of proposal structs. You'll need to format the data, converting BigNumber values for amounts and timestamps into readable formats. For each proposal, construct the IPFS gateway URL to the PDF using the stored CID (e.g., https://gateway.pinata.cloud/ipfs/${proposalCid}). Render this list in a component, providing links for users to view the full proposal documents directly.

Implement wallet connection using the Ethers.js BrowserProvider. A common pattern is to create a "Connect Wallet" button that triggers provider.send("eth_requestAccounts", []) to request account access. Listen for account and chain changes using the accountsChanged and chainChanged events to update the UI state accordingly. Ensure your frontend validates that the user is connected to the correct network (e.g., Sepolia testnet) that your contract is deployed on.

Finally, add error handling and user feedback. Wrap contract calls in try-catch blocks to gracefully handle transaction rejections or RPC errors. Use loading states to indicate when a transaction is pending or an IPFS upload is in progress. This creates a polished user experience for a fully operational grant portal where proposals are stored immutably on-chain with their supporting documents permanently accessible via IPFS.

GRANT PROPOSAL PORTAL

Smart Contract Function Reference

Core public functions for the GrantProposalPortal.sol contract, detailing access control and key operations.

FunctionAccessDescriptionKey Parameters

submitProposal

Public

Creates a new grant proposal for community review.

title, description, requestedAmount, recipient

castVote

Token Holder

Allows a governance token holder to vote on a live proposal.

proposalId, support (true/false)

queueProposal

Governance Timelock

Queues a successful proposal for execution after a delay.

proposalId

executeProposal

Governance Timelock / Public

Executes a queued proposal, transferring funds to the recipient.

proposalId

cancelProposal

Proposer / Admin

Allows the proposer or an admin to cancel a proposal before execution.

proposalId

updateQuorum

Admin Only

Updates the minimum votes required for a proposal to pass.

newQuorum

updateVotingDelay

Admin Only

Updates the delay (in blocks) before voting starts on a proposal.

newVotingDelay

getProposalState

Public

Returns the current state (e.g., Pending, Active, Succeeded) of a proposal.

proposalId

testing-deployment
SMART CONTRACT DEVELOPMENT

Step 6: Testing and Deploying to a Testnet

This step covers the essential process of testing your transparent grant proposal smart contracts and deploying them to a public test network before mainnet launch.

Before deploying your grant portal to a live network, you must rigorously test your smart contracts. This involves writing and running unit tests to verify the core logic of your GrantRegistry and GrantProposal contracts. Use a testing framework like Hardhat or Foundry to simulate interactions such as creating a proposal, submitting milestones, voting on proposals, and distributing funds. Test edge cases thoroughly, including failed transactions, unauthorized access attempts, and contract upgrade scenarios. A comprehensive test suite is your primary defense against costly bugs and security vulnerabilities.

After successful local testing, deploy your contracts to a public testnet like Sepolia or Goerli. This step validates that your contracts function correctly in an environment that mimics mainnet conditions, including real gas costs and network latency. Use environment variables to manage your deployer's private key and RPC endpoint. A typical deployment script will first deploy the implementation logic contract, then a proxy contract (if using an upgradeable pattern like TransparentUpgradeableProxy), and finally any helper contracts. Always verify your contract source code on the testnet block explorer after deployment.

With your contracts live on a testnet, you can now test the complete integration with your frontend dApp. Connect your web interface (e.g., built with wagmi and Viem) to the testnet contract addresses. Perform end-to-end user flows: connect a test wallet (using test ETH from a faucet), create a proposal, and simulate the entire grant lifecycle. This integration testing uncovers issues with event listening, transaction formatting, and state management that unit tests might miss. Document any discrepancies between expected and actual behavior for debugging.

TRANSPARENT GRANT PORTAL

Frequently Asked Questions

Common questions and troubleshooting for developers setting up a transparent grant proposal portal using smart contracts and on-chain governance.

A transparent grant portal is a decentralized application (dApp) that manages the entire grant lifecycle on-chain. It uses smart contracts to create a trustless system where:

  • Proposals are submitted as on-chain transactions, making them immutable and publicly verifiable.
  • Voting is conducted using governance tokens (e.g., ERC-20, ERC-721), with votes recorded directly on the blockchain.
  • Funding is distributed automatically via the smart contract upon proposal approval, eliminating manual intervention.
  • Milestone tracking is often implemented, releasing funds incrementally as verifiable deliverables are completed.

This architecture ensures full auditability, reduces administrative overhead, and aligns incentives between grantors and grantees. Popular frameworks for building such portals include OpenZeppelin Governor contracts and Tally's governance tooling.

conclusion-next-steps
IMPLEMENTATION SUMMARY

Conclusion and Next Steps

You have successfully built a transparent grant proposal portal using smart contracts and a frontend interface. This guide covered the core architecture, security considerations, and deployment steps.

Your portal now provides a foundational, on-chain framework for managing grant proposals. Key features implemented include a proposal submission system with structured metadata, a transparent voting mechanism using token-based governance, and a secure fund distribution process via a multi-signature treasury. The frontend, built with a framework like Next.js or Vite, connects to this contract layer using libraries such as wagmi and viem, providing a user-friendly interface for creators and voters. This setup ensures all proposal data, votes, and fund movements are permanently recorded and publicly verifiable on the blockchain.

To enhance your portal, consider these next steps. First, integrate IPFS or Arweave for decentralized, cost-effective storage of detailed proposal documents and milestone proofs. Second, implement more sophisticated governance features, such as quadratic voting or conviction voting, to improve decision-making quality. Third, add Chainlink Automation or Gelato to automate tasks like proposal expiration checks and milestone payouts. Finally, consider bridging to other networks using cross-chain messaging protocols like LayerZero or Axelar to create a multi-chain grant ecosystem, expanding your donor and applicant base.

For ongoing maintenance and community trust, establish clear operational procedures. This includes defining roles for multi-signature signers, setting up monitoring with tools like Tenderly or OpenZeppelin Defender for contract alerts, and publishing regular transparency reports. Engage with your community through forums like Commonwealth or Discourse to gather feedback on the governance process. The code and concepts from this guide serve as a starting point; the most successful grant platforms evolve through continuous iteration based on real-world use and community input.