Overview
CrowdPay's platform fee is currently hardcoded. As the platform grows, the community of creators and contributors should have a voice in fee changes, and the fee structure itself should be transparent and verifiable on-chain. This issue implements three things: a Soroban fee registry contract that stores the platform fee percentage on-chain, a revenue share system that distributes a percentage of platform fees back to high-volume creators, and a lightweight on-chain governance mechanism where CrowdPay token holders can propose and vote on fee changes.
What needs to be built
contracts/soroban/fee_registry/ — Soroban contract (Rust)
-
Contract storage: platform_fee_bps: u32, creator_share_bps: u32, governance_token: Address, admin: Address, pending_proposal: Option<FeeProposal>
-
FeeProposal: { id: u32, proposed_fee_bps: u32, proposed_creator_share_bps: u32, votes_for: i128, votes_against: i128, deadline: u64, status: ProposalStatus }
-
Functions:
get_fee() -> u32 — returns platform_fee_bps — called by the CrowdPay backend before every contribution to get the current fee
propose_change(new_fee_bps, new_creator_share_bps) — callable by any governance token holder with balance > 1,000 tokens; creates a FeeProposal with deadline = now + 7 days
vote(proposal_id, in_favor: bool) — callable by any token holder; vote weight = token balance at the time of the vote (snapshot via token contract balance())
execute_proposal(proposal_id) — callable by anyone after deadline; if votes_for > votes_against AND votes_for > 10% of total token supply: updates platform_fee_bps and creator_share_bps; else: marks as failed
admin_set_fee(fee_bps, creator_share_bps) — callable by admin only; bypasses governance (emergency use)
backend/src/services/feeRegistry.js — Backend integration
-
On startup and every 5 minutes: call get_fee() on the contract; cache in Redis
-
Contribution fee calculation: use cached platform_fee_bps from the contract — not a hardcoded value
-
Creator revenue share: at campaign withdrawal time, compute creator_share = collected_fees_for_this_campaign * (creator_share_bps / 10000); include an additional Payment operation in the withdrawal transaction sending creator_share from the platform fee wallet to the creator's wallet
backend/src/services/governance.js — Governance API
-
GET /api/governance/proposals — list all proposals (active and historical) from contract state via getLedgerEntries
-
GET /api/governance/proposals/:id — single proposal detail: votes for/against, deadline, token holder participation rate, current outcome projection
-
POST /api/governance/proposals/:id/vote — user votes:
- Validates user holds governance tokens in their connected Stellar wallet (checked via Horizon account balances)
- Calls
vote(proposal_id, in_favor) on the contract — user must sign the transaction via Freighter
-
GET /api/governance/fee — returns current fee from Redis cache + the on-chain contract ID for verification
Frontend
-
/governance page:
- Current fee display:
Platform fee: X bps (Y%) — with a link to view the contract on Stellar Expert
- Creator revenue share:
Creators receive Z% of platform fees collected on their campaigns
- Active proposal (if any): vote count, deadline countdown, "Vote For" / "Vote Against" buttons (requires Freighter connection)
- Proposal history: table of past proposals with outcome
-
"Create Proposal" form (gated by 1,000 token balance check): proposed fee bps, proposed creator share bps, rationale text (stored off-chain in proposals table)
Database migrations
-
governance_proposals_meta: id, stellar_proposal_id, proposer, rationale_text, created_at
-
governance_votes_log: id, proposal_id, voter_public_key, in_favor, token_balance_at_vote, voted_at
Acceptance criteria
Overview
CrowdPay's platform fee is currently hardcoded. As the platform grows, the community of creators and contributors should have a voice in fee changes, and the fee structure itself should be transparent and verifiable on-chain. This issue implements three things: a Soroban fee registry contract that stores the platform fee percentage on-chain, a revenue share system that distributes a percentage of platform fees back to high-volume creators, and a lightweight on-chain governance mechanism where CrowdPay token holders can propose and vote on fee changes.
What needs to be built
contracts/soroban/fee_registry/— Soroban contract (Rust)Contract storage:
platform_fee_bps: u32,creator_share_bps: u32,governance_token: Address,admin: Address,pending_proposal: Option<FeeProposal>FeeProposal:{ id: u32, proposed_fee_bps: u32, proposed_creator_share_bps: u32, votes_for: i128, votes_against: i128, deadline: u64, status: ProposalStatus }Functions:
get_fee() -> u32— returnsplatform_fee_bps— called by the CrowdPay backend before every contribution to get the current feepropose_change(new_fee_bps, new_creator_share_bps)— callable by any governance token holder with balance > 1,000 tokens; creates aFeeProposalwithdeadline = now + 7 daysvote(proposal_id, in_favor: bool)— callable by any token holder; vote weight = token balance at the time of the vote (snapshot via token contractbalance())execute_proposal(proposal_id)— callable by anyone afterdeadline; ifvotes_for > votes_againstANDvotes_for > 10% of total token supply: updatesplatform_fee_bpsandcreator_share_bps; else: marks asfailedadmin_set_fee(fee_bps, creator_share_bps)— callable byadminonly; bypasses governance (emergency use)backend/src/services/feeRegistry.js— Backend integrationOn startup and every 5 minutes: call
get_fee()on the contract; cache in RedisContribution fee calculation: use cached
platform_fee_bpsfrom the contract — not a hardcoded valueCreator revenue share: at campaign withdrawal time, compute
creator_share = collected_fees_for_this_campaign * (creator_share_bps / 10000); include an additionalPaymentoperation in the withdrawal transaction sendingcreator_sharefrom the platform fee wallet to the creator's walletbackend/src/services/governance.js— Governance APIGET /api/governance/proposals— list all proposals (active and historical) from contract state viagetLedgerEntriesGET /api/governance/proposals/:id— single proposal detail: votes for/against, deadline, token holder participation rate, current outcome projectionPOST /api/governance/proposals/:id/vote— user votes:vote(proposal_id, in_favor)on the contract — user must sign the transaction via FreighterGET /api/governance/fee— returns current fee from Redis cache + the on-chain contract ID for verificationFrontend
/governancepage:Platform fee: X bps (Y%)— with a link to view the contract on Stellar ExpertCreators receive Z% of platform fees collected on their campaigns"Create Proposal" form (gated by 1,000 token balance check): proposed fee bps, proposed creator share bps, rationale text (stored off-chain in
proposalstable)Database migrations
governance_proposals_meta:id,stellar_proposal_id,proposer,rationale_text,created_atgovernance_votes_log:id,proposal_id,voter_public_key,in_favor,token_balance_at_vote,voted_atAcceptance criteria
get_fee()on the contract returns the correctplatform_fee_bps— contribution fee calculation matches the contract value exactlyvotes_forthanvotes_againstAND exceeds 10% participation threshold is executed —platform_fee_bpsupdates on the contract afterexecute_proposalis calledfailedeven ifvotes_for > votes_against— governance quorum is enforcedpropose_changecorrectly rejects a caller with fewer than 1,000 governance tokens — returns a Soroban auth erroradmin_set_feecall on the contract — no stale fee used for contributions