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 Proposal Submission Portal on a Blockchain

A technical tutorial for developers to build a decentralized portal for submitting, tracking, and managing scientific research proposals on-chain.
Chainscore © 2026
introduction
TUTORIAL

Setting Up a Proposal Submission Portal on a Blockchain

A technical guide to building a secure, on-chain portal for decentralized governance proposals.

An on-chain proposal portal is a decentralized application (dApp) that allows community members to submit, fund, and track governance proposals directly on the blockchain. Unlike off-chain forums, these portals ensure proposals are immutable, transparent, and executable once approved. They are a core component of DAOs (Decentralized Autonomous Organizations) and protocol governance, enabling stakeholders to propose changes to smart contracts, treasury allocations, or protocol parameters. Popular frameworks for building these systems include OpenZeppelin Governor, Compound's Governor Bravo, and Aragon OSx.

The architecture of a proposal portal typically involves three key smart contracts: a Governor contract that manages the proposal lifecycle, a Token contract (often an ERC-20 or ERC-721) that determines voting power, and a Treasury contract that holds assets for approved proposals. When a user submits a proposal, they bundle one or more encoded function calls—such as transferring funds from a treasury or upgrading a contract—into a transaction. This proposal is stored on-chain with a unique ID, a description hash, and a target execution timestamp.

To set up a basic portal, you first need to deploy your governance token. Using Solidity and OpenZeppelin contracts, you can extend ERC20Votes to create a token that supports vote delegation and snapshotting. Next, deploy a Governor contract. OpenZeppelin's Governor contract provides a modular base. A minimal setup involves initializing it with your token address and setting proposal parameters like votingDelay (blocks before voting starts) and votingPeriod (blocks voting is active).

The proposal submission function is critical. In Solidity, a proposer calls propose on the Governor contract, passing an array of target addresses, values, and calldata for the proposed actions. They must also include a description, often hashed and stored on-chain, with the full text hosted on IPFS for transparency. The proposer must hold a minimum voting power threshold, preventing spam. Here's a simplified interface:

solidity
function propose(
    address[] memory targets,
    uint256[] memory values,
    bytes[] memory calldatas,
    string memory description
) public returns (uint256 proposalId);

After submission, the proposal enters a queue, then a voting period where token holders cast votes. Once voting ends, the proposal can be executed if it meets quorum and passes the required vote threshold. The portal's frontend, built with a library like wagmi or ethers.js, connects users' wallets to interact with these contracts. It displays active proposals, voting status, and allows users to submit new proposals through a web form that constructs the proper transaction calldata.

Security and gas optimization are paramount. Use timelocks for sensitive actions to allow a review period before execution. Implement proposal thresholds and quorum requirements to ensure sufficient community engagement. For production, consider using established audit frameworks like OpenZeppelin Governor, which has been battle-tested in protocols like Uniswap and Compound. Always conduct thorough testing on a testnet (like Sepolia or Goerli) and consider a bug bounty program before mainnet deployment.

prerequisites
TECH STACK

Prerequisites and Tech Stack

A proposal submission portal is a full-stack Web3 application. This guide outlines the core technologies required to build a secure and functional system.

Building a blockchain proposal portal requires a full-stack Web3 development approach. The core components are a smart contract for on-chain governance logic, a frontend application for user interaction, and a backend service for indexing and processing. You'll need proficiency in languages like Solidity for the contract, JavaScript/TypeScript with a framework like React or Next.js for the frontend, and Node.js for backend tasks. Familiarity with Ethereum tooling such as Hardhat or Foundry is essential for development and testing.

The smart contract is the system's backbone, defining the proposal lifecycle: submission, voting, and execution. It must implement standards like OpenZeppelin's Governor contract for a secure foundation. You'll handle key concepts such as proposal calldata, voting power derived from token balances (e.g., ERC-20 or ERC-721), and timelocks for secure execution. Development requires a local test environment (e.g., Hardhat Network) and testnets like Sepolia or Goerli for deployment.

For the frontend, you must integrate a Web3 provider library like ethers.js or viem to connect users' wallets (MetaMask, WalletConnect) and interact with the contract. The UI needs to fetch live blockchain data—proposal state, voter balances, and voting results. This is typically done by querying the contract directly or using a The Graph subgraph for efficient, indexed data. The frontend must also handle transaction signing and gas estimation.

A backend service is often necessary for tasks that are inefficient or impossible on-chain. This includes off-chain signature validation for gasless voting (using EIP-712), sending notifications, aggregating historical data, or managing IPFS for proposal metadata. This service can be built with Node.js, Python, or similar, and should use a reliable RPC provider (Alchemy, Infura) for consistent blockchain access.

Finally, consider the deployment and infrastructure stack. Smart contracts are deployed to your target chain (Ethereum L1, Arbitrum, Optimism). The frontend can be hosted on decentralized storage (IPFS via Fleek or Spheron) or traditional services (Vercel, AWS). The backend service requires a server environment. Security audits for the smart contract and diligent private key management for any administrative functions are non-negotiable prerequisites for a production system.

architecture-overview
SYSTEM ARCHITECTURE AND DATA FLOW

Setting Up a Proposal Submission Portal on a Blockchain

A technical guide to architecting a decentralized governance portal for on-chain proposal submission, voting, and execution.

A blockchain-based proposal portal is a full-stack application that bridges user-friendly interfaces with on-chain governance contracts. Its core architecture consists of three layers: the frontend client (a web or mobile app), a backend service layer (often a serverless API or indexer), and the on-chain smart contracts. The frontend handles user interaction, the backend manages data aggregation and transaction relaying, while the smart contracts enforce the immutable governance rules. This separation ensures the UI remains responsive while delegating security-critical logic to the blockchain.

The data flow begins when a user drafts a proposal. Metadata like the title, description, and discussion links are typically stored off-chain in decentralized storage (e.g., IPFS or Arweave) to avoid bloating the chain. Only a content hash (like a CID) and essential parameters (proposal type, voting options) are sent to the governance smart contract via a transaction. The backend service listens for these contract events, indexes the new proposal data, and serves it to the frontend in a queryable format, creating a seamless user experience.

Key smart contract functions must be implemented for core operations. For a basic portal, you need submitProposal(bytes32 _ipfsHash, uint _voteType), castVote(uint _proposalId, uint _support), and executeProposal(uint _proposalId). Using a framework like OpenZeppelin's Governor contract accelerates development. The backend must securely manage transaction signing, often using a dedicated wallet with a private key or a service like Gelato for meta-transactions to sponsor gas fees for users, lowering the barrier to participation.

Security and validation are paramount. The frontend and backend should validate proposal data (e.g., minimum title length, valid voting options) before submitting to the chain to prevent failed transactions and wasted gas. Implement access control using modifiers like onlyProposer or onlyMember in your contracts. For transparency, all proposal state changes—from submission to execution—should emit events that your indexer captures, providing a verifiable audit trail on the frontend.

To handle real-time updates, the portal needs an efficient data indexing strategy. Using a subgraph with The Graph protocol is a common solution. You define a schema for proposals and votes, then write mappings to process contract events into queryable entities. This allows the frontend to fetch complex, filtered data with simple GraphQL queries, such as fetching all active proposals or a user's voting history, without directly polling the blockchain RPC, which is slow and rate-limited.

Finally, consider advanced features like proposal templates (standardized formats for budget requests or parameter changes), simulation (using Tenderly or a forked network to preview vote outcomes), and cross-chain governance (using LayerZero or Axelar for multi-chain DAOs). The portal's architecture must be modular to accommodate these additions. Always audit your smart contracts and implement comprehensive testing with tools like Hardhat or Foundry before deploying to mainnet.

contract-design
CORE ARCHITECTURE

Step 1: Designing the Proposal Smart Contract

The smart contract is the foundational layer of any on-chain governance portal. This step defines the data structures, submission logic, and state transitions for proposals.

A proposal submission contract must define the core struct that represents a proposal. This structure typically includes fields like a unique id, a title, a detailed description, the proposer address, a timestamp for submission, and a status (e.g., Pending, Active, Executed). For more complex systems, you may add fields for an executionPayload (calldata for the action to take if the proposal passes) and a snapshotBlock for voter eligibility. Using Solidity, the initial definition might look like:

solidity
struct Proposal {
    uint256 id;
    string title;
    string description;
    address proposer;
    uint256 submitTime;
    Status status;
    bytes executionPayload;
}

The contract's state is managed through key mappings and variables. You will need a proposalCount to generate unique IDs and a mapping like proposals(uint256 => Proposal) to store proposals by ID. To prevent spam, implement a proposal deposit mechanism, storing the amount in a proposalDeposit variable and requiring it upon submission, with refunds contingent on the proposal reaching a voting stage. Access control is critical; use OpenZeppelin's Ownable or a custom modifier to restrict certain functions, like finalizing proposals, to a designated governor or executor address.

The core function is submitProposal(string memory _title, string memory _description, bytes memory _payload). This function should:

  • Increment the proposalCount.
  • Transfer the required deposit from msg.sender.
  • Create a new Proposal struct with a Pending status and store it.
  • Emit an event (ProposalSubmitted) containing the new proposal's ID and details for off-chain indexing. This function must include checks, such as ensuring the deposit is met and the title/description are non-empty strings. The executionPayload can be empty for signaling proposals.

After submission, proposals usually move through a lifecycle. You'll need internal or privileged functions to update a proposal's status. A reviewProposal(uint256 _proposalId, Status _newStatus) function, callable only by a guardian role, can move a proposal from Pending to Active (ready for voting) or Rejected. A separate executeProposal(uint256 _proposalId) function would verify the proposal is in an Approved state (set by a separate voting contract) and use call or delegatecall to execute the stored executionPayload. Always emit status change events for transparency.

Security considerations are paramount. Use checks-effects-interactions patterns to prevent reentrancy. Ensure the executeProposal function is protected against replay attacks, typically by updating the proposal status to Executed before making the external call. For the deposit logic, consider using OpenZeppelin's ReentrancyGuard. Thoroughly test the contract with tools like Foundry or Hardhat, simulating malicious proposers and guardians. The completed contract forms the immutable backbone for the subsequent steps: building the frontend interface and connecting to a voting mechanism.

frontend-integration
TUTORIAL

Step 2: Building the Frontend Interface

This guide walks through creating a React-based web portal for submitting on-chain governance proposals, connecting a wallet, and interacting with a smart contract.

The frontend portal is the user-facing application that allows community members to create, view, and submit governance proposals. For this tutorial, we'll use React with TypeScript and Vite for a modern development setup. We'll integrate wagmi and viem for blockchain interactions and RainbowKit for wallet connection. Start by initializing the project: npm create vite@latest proposal-portal -- --template react-ts. Then, install the essential Web3 dependencies: npm install wagmi viem @rainbow-me/rainbowkit.

The core of the frontend is the wallet connection and network configuration. Using wagmi and RainbowKit, you configure the chains your app supports (e.g., Ethereum Mainnet, Arbitrum, Optimism) and set up a provider. This allows users to connect with wallets like MetaMask, Coinbase Wallet, or WalletConnect. The configuration involves wrapping your app with WagmiProvider and RainbowKitProvider, passing in a public RPC URL for the desired chain. This setup abstracts away the complexity of managing account state and chain switching.

With the wallet connected, the next step is to interact with the governance smart contract. You'll use the Contract ABI (Application Binary Interface) generated during the smart contract compilation. Using wagmi's useContractWrite and useContractRead hooks, you can create typed functions for each contract method. For submitting a proposal, you would create a form that captures the proposal data (title, description, calldata) and then call a write function that triggers a transaction from the user's wallet, requiring their signature.

A good UI provides feedback at every step. Implement transaction state tracking using wagmi's useWaitForTransaction hook to show loading states, success messages, or error alerts. It's also crucial to read on-chain data to populate the interface. Use useContractRead to fetch existing proposals, their status (Pending, Active, Executed), and vote totals. Display this data in a list or table, giving users context about the current state of governance before they submit a new proposal.

Finally, consider security and user experience best practices. Always validate form data on the client side before submitting a transaction to avoid unnecessary gas fees. Use environment variables (e.g., .env) to store your contract address and RPC URLs. For production, consider adding features like transaction history, proposal filtering, and integration with IPFS (using a service like Pinata) for storing long proposal descriptions off-chain, referencing the IPFS CID (Content Identifier) in the on-chain transaction.

metadata-storage
DECENTRALIZED STORAGE

Step 3: Handling Proposal Metadata with IPFS

Learn how to store proposal details off-chain using IPFS, ensuring transparency and immutability without bloating the blockchain.

On-chain storage is expensive and limited. For a proposal portal, storing detailed descriptions, images, or supporting documents directly on the blockchain is impractical. The standard solution is to store a content identifier (CID) on-chain while the actual metadata lives on the InterPlanetary File System (IPFS), a decentralized storage network. This approach separates the immutable, consensus-critical data (the proposal's existence and basic parameters) from its potentially large descriptive content, keeping gas costs low while maintaining verifiable data integrity.

The workflow is straightforward. First, you create a JSON object containing the proposal's metadata, such as title, description, externalLink, and discussionURL. This JSON is then uploaded to an IPFS node or pinning service like Pinata, Infura, or web3.storage. Upon successful upload, the service returns a unique CID—a cryptographic hash of the content. This CID is what your smart contract's createProposal function will store. Anyone can use this CID to fetch the exact, original metadata from the IPFS network, guaranteeing the data has not been altered.

Here is a simplified example using the Pinata SDK to pin JSON metadata and retrieve the CID for on-chain submission:

javascript
import axios from 'axios';
const pinataJWT = 'YOUR_PINATA_JWT';
const metadata = {
  title: "Grant Proposal: Developer Tooling",
  description: "Fund the development of...",
  category: "Infrastructure"
};
const url = `https://api.pinata.cloud/pinning/pinJSONToIPFS`;
const response = await axios.post(url, metadata, {
  headers: { Authorization: `Bearer ${pinataJWT}` }
});
const proposalCID = response.data.IpfsHash; // e.g., "QmXYZ..."
// Now pass `proposalCID` to your smart contract

When a user or a frontend queries the blockchain for a proposal, it receives the on-chain data and the CID. The frontend can then reconstruct the gateway URL to fetch the full metadata. For CID QmXYZ..., a public gateway URL would be https://ipfs.io/ipfs/QmXYZ.... For better performance and reliability, you should use a dedicated gateway service or your own IPFS node. This ensures the metadata is always accessible for rendering the proposal's details on your portal's interface.

It is critical to understand that IPFS is not a persistence guarantee. Data must be pinned to prevent garbage collection. Using a paid pinning service is the most reliable method for production applications, as they ensure your data remains available. The on-chain CID acts as a permanent proof of what the proposal data was at submission time. If the IPFS data disappears, the CID still allows anyone to verify that the retrieved data matches the original submission, upholding the system's transparency and auditability.

In summary, combining IPFS for metadata with on-chain CIDs creates a scalable architecture for proposal systems. The blockchain secures the record of events and the content hash, while IPFS handles the storage of rich data. This pattern is used by major DAO platforms like Aragon and Snapshot, and is essential for building sustainable, feature-rich governance applications without incurring prohibitive gas costs.

testing-deployment
PRODUCTION READINESS

Step 4: Testing and Deploying the System

This final step transitions your proposal portal from a local development environment to a live, secure, and user-ready application on the blockchain.

Before deployment, comprehensive testing is critical. Start with unit tests for your core smart contract logic using frameworks like Hardhat or Foundry. Test key functions such as submitProposal, vote, and executeProposal. For a proposal portal, you must also simulate governance actions, including testing the conditions for proposal success and failure. Use a local blockchain network (e.g., Hardhat Network) to run these tests quickly and without gas costs. Ensure your tests cover edge cases, such as voting after a deadline or a user attempting to vote twice.

Next, conduct integration and end-to-end testing. This involves testing the interaction between your smart contracts and the frontend dApp. Use tools like Playwright or Cypress to automate browser interactions, simulating a user connecting their wallet, submitting a proposal, and casting a vote. It's essential to test with a forked mainnet environment (using Alchemy or Infura) to ensure your contracts behave correctly with real token balances and existing contract states. This step catches integration bugs that unit tests might miss.

For deployment, choose your target network. For final testing, deploy first to a testnet like Sepolia or Goerli. Use environment variables to manage private keys and RPC URLs securely. A typical Hardhat deployment script (scripts/deploy.js) will handle contract compilation and deployment. After deployment, verify and publish your contract source code on the network's block explorer (e.g., Etherscan). Verification is non-negotiable for trust and transparency, allowing users to audit the live contract code.

Once verified, configure your frontend application to point to the live contract addresses on your chosen network. Update your dApp's configuration (e.g., in src/config.js) with the new contract ABI and address. Implement robust error handling and user notifications for transaction states (pending, confirmed, failed). Consider using a service like The Graph to index and query proposal data efficiently, as reading events directly from the chain can be slow for complex queries.

Finally, establish ongoing maintenance and monitoring. Set up alerts for critical contract events using a service like OpenZeppelin Defender. Plan for upgradeability if required, using proxy patterns like the Transparent Proxy or UUPS, and clearly communicate upgrade governance to users. Your portal is now live, but its security and functionality depend on continuous oversight and a clear plan for future iterations based on community feedback.

ARCHITECTURE DECISION

On-Chain vs. Off-Chain Proposal Data Storage

Comparison of core technical and economic trade-offs for storing proposal data in a blockchain submission portal.

FeatureOn-Chain StorageHybrid Storage (IPFS + Anchors)Off-Chain Database

Data Immutability & Verifiability

Storage Cost per 1MB Proposal

$50-200 (Ethereum Mainnet)

$0.05-0.10 (IPFS pinning) + $2-5 (anchor tx)

< $0.01

Retrieval Speed

< 1 sec (via RPC)

2-5 sec (fetch from IPFS)

< 100 ms

Censorship Resistance

Developer Complexity

High (gas mgmt., calldata)

Medium (IPFS client, event handling)

Low (standard API)

Data Availability Guarantee

Full (by chain)

High (decentralized IPFS)

Depends on provider SLA

Long-Term Data Persistence (10+ yrs)

Full (by chain consensus)

Conditional (requires ongoing pinning)

Conditional (requires DB maintenance)

Suitable for Large Files (e.g., PDFs, images)

PROPOSAL SUBMISSION PORTAL

Common Development Issues and Solutions

Building a secure and efficient on-chain governance portal involves navigating smart contract interactions, gas costs, and frontend integration. This guide addresses frequent developer challenges.

This is often caused by underestimating the gas required for complex proposal logic or large calldata. The gasLimit must account for the entire execution path, including any external contract calls made by the proposal.

Common fixes:

  • Estimate gas dynamically: Use eth_estimateGas via your provider (e.g., Ethers.js contract.estimateGas.submitProposal(...)) and add a buffer (e.g., 20%).
  • Optimize proposal logic: Move heavy computations off-chain, store data in events or IPFS, and pass only hashes on-chain.
  • Check for loops: Unbounded loops in proposal validation or execution will cause unpredictable gas costs. Use pagination or require a maximum array size.

Example with Ethers.js:

javascript
const estimatedGas = await governanceContract.estimateGas.submitProposal(targets, values, calldatas, description);
const tx = await governanceContract.submitProposal(targets, values, calldatas, description, { gasLimit: estimatedGas.mul(110).div(100) // +10% buffer });
PROPOSAL PORTAL SETUP

Frequently Asked Questions

Common technical questions and solutions for developers building or integrating a proposal submission system on-chain.

A blockchain proposal submission portal is a decentralized application (dApp) that allows users to create, submit, and manage governance or funding proposals directly on-chain. It typically consists of a smart contract backend for logic and state management, and a frontend interface for user interaction.

Core workflow:

  1. A user connects their wallet (e.g., MetaMask) to the portal's frontend.
  2. They fill out a proposal form (title, description, funding amount).
  3. The frontend constructs a transaction, calling functions like submitProposal() on the smart contract.
  4. The contract validates the submission (e.g., checks staked tokens, proposal fee) and emits an event.
  5. The proposal data (metadata, proposer, timestamp) is stored on-chain, often with a pointer to IPFS or Arweave for larger documents.
  6. The portal's frontend listens for these events to update its UI in real-time.
conclusion-next-steps
IMPLEMENTATION SUMMARY

Conclusion and Next Steps

You have now built the core components of a proposal submission portal. This guide covered the essential steps from smart contract design to frontend integration.

Your portal's foundation is a secure and transparent smart contract. The ProposalRegistry contract you deployed handles the core logic: - Submitting proposals with metadata - Managing a deposit and refund system - Tracking proposal statuses. This on-chain record ensures immutability and verifiability, which is critical for governance processes. Remember to thoroughly audit your contract and consider using established libraries like OpenZeppelin for access control and security.

The next phase involves enhancing functionality. Consider integrating with decentralized storage solutions like IPFS or Arweave for proposal documents, keeping only content hashes on-chain to reduce gas costs. You could also add features like: - Voting mechanisms using token-weighted or quadratic voting - Multi-signature approval workflows for councils - Automated treasury disbursement upon proposal passage. Each addition requires careful smart contract design to maintain security and gas efficiency.

For the frontend, focus on improving the user experience and accessibility. Implement wallet connection for networks beyond Ethereum Mainnet, such as Polygon or Arbitrum, using Wagmi v2 or RainbowKit. Add real-time updates for proposal status using The Graph for indexing or Socket.io for notifications. Ensure your UI is clear and guides users through the submission process, displaying gas estimates and transaction confirmations.

Finally, prepare your portal for production. Conduct rigorous testing on a testnet like Sepolia or Holesky, simulating high load and edge cases. Set up monitoring tools like Tenderly or OpenZeppelin Defender to track contract events and alert you to suspicious activity. Document your portal's API and submission guidelines clearly for your community. A well-built portal becomes a trusted tool for decentralized decision-making.

How to Build a Blockchain Proposal Portal for DeSci | ChainScore Guides