A trustless, decentralized platform for posting tasks, submitting work, and managing bounty payments on the Stellar blockchain using Soroban smart contracts.
🎯 Problem Traditional bounty systems require trust in centralized platforms or intermediaries. Contributors risk not getting paid, and task posters have no guarantee of quality work. There's no transparent, trustless way to manage task-based payments.
✨ Solution TaskBounty provides a Soroban smart contract system that:
Escrows funds when tasks are posted (using Stellar native assets or tokens) Enables transparent submission of work with IPFS/Arweave links Automates payouts upon approval Handles disputes through a decentralized arbitrator mechanism Aligns with Drips for direct contributor funding Low fees thanks to Stellar's efficient network Fast finality with 3-5 second confirmation times 🌟 Why Stellar? Low Transaction Costs: Fractions of a cent per transaction Fast Finality: 3-5 second confirmation times Built-in DEX: Native token support and atomic swaps Soroban: Modern Rust-based smart contracts with WebAssembly Scalability: Thousands of transactions per second Developer Friendly: Excellent tooling and documentation 🏗️ Architecture
┌─────────────────────────────────────────────────────────────────────┐
│ Frontend (Next.js) │
│ Task Board │ Create Task │ Submit Work │ Wallet Connect │
└──────────────────────────┬──────────────────────────────────────────┘
│ Stellar Wallets Kit (RPC calls)
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Stellar Network (Soroban RPC) │
└──────────┬──────────────────────────────────┬───────────────────────┘
│ │
▼ ▼
┌──────────────────────┐ ┌───────────────────────┐
│ TaskBounty │ calls │ DisputeResolver │
│ Contract │ ───────► │ Contract │
│ │ │ │
│ • create_task │ │ • raise_dispute │
│ • submit_work │ │ • resolve_dispute │
│ • approve_submission│ │ • manage_arbitrators │
│ • reject_submission │ └───────────────────────┘
│ • cancel_task │
│ • get_task │
│ • get_submission │
└──────────────────────┘
│
│ token transfers via SAC interface
▼
┌──────────────────────┐
│ Token Contract │
│ (XLM / SAC token) │
│ Escrowed rewards │
└──────────────────────┘
┌─────────┐
│ OPEN │ ◄── create_task() — reward escrowed
└────┬────┘
│ submit_work()
▼
┌────────────┐
│ IN PROGRESS│
└─────┬──────┘
┌─────────────┤
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ APPROVED │ │ REJECTED │
│ (payout) │ │ (re-open)│
└──────────┘ └──────────┘
│
│ raise_dispute()
▼
┌──────────┐
│ DISPUTED │ ──► DisputeResolver ──► COMPLETED or refund
└──────────┘
poster cancels anytime before approval
│
▼
┌──────────┐
│CANCELLED │ (reward refunded to poster)
└──────────┘
Poster Contract Token (SAC)
│ │ │
│── create_task() ──────►│ │
│ │── transfer_from() ──►│ escrow reward
│◄── task_id ────────────│ │
│ │ │
Contributor │ │
│── submit_work() ──────►│ │
│◄── submission_id ──────│ │
│ │ │
Poster │ │
│── approve_submission()►│ │
│ │── transfer() ───────►│ pay contributor
│ │ emit Approved │
│◄── tx confirmed ───────│ │
Contributor TaskBounty DisputeResolver Arbitrator
│ │ │ │
│── raise_dispute() ►│ │ │
│ │── forward dispute ──►│ │
│ │ │◄── resolve() ─────│
│ │ │ (true/false) │
│ │◄── resolution ────────│ │
│ │ │ │
│ │ if true: pay contrib │ │
│ │ if false: refund poster │
Task-Bounty/
├── contract/
│ └── contracts/
│ └── hello-world/ # Soroban contract
│ └── src/
│ ├── lib.rs # Contract entry points & public API
│ ├── types.rs # Task, Submission, Dispute, Error types
│ ├── storage.rs # Storage key helpers
│ ├── task.rs # Task creation & cancellation
│ ├── submission.rs # Submit, approve, reject
│ ├── dispute.rs # Dispute handling
│ └── events.rs # Event emission helpers
├── frontend/ # Next.js frontend
│ └── src/
│ ├── app/ # Next.js app router pages
│ ├── components/ # Shared UI components
│ ├── hooks/ # Stellar wallet integration
│ └── lib/ # Stellar RPC utilities
├── CONTRIBUTING.md
├── SETUP.md
├── TROUBLESHOOTING.md
├── CONTRACT_API.md
└── README.md
Key Features Task Posting: Create tasks with escrowed rewards (XLM or any Stellar token) Work Submission: Contributors submit IPFS/Arweave links to their work Approval System: Task posters review and approve/reject Auto Payout: Approved work triggers instant payment via Stellar Dispute Resolution: Decentralized arbitration for conflicts Multi-submission Support: Multiple contributors can compete Deadline Management: Time-based task expiration Token Flexibility: Support for XLM and any Stellar Asset Contract (SAC) token 🚀 Quick Start Prerequisites
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-unknown-unknown
cargo install --locked stellar-cli --features opt Installation
git clone cd task-bounty
stellar contract build Build
stellar contract build
stellar contract optimize --wasm target/wasm32-unknown-unknown/release/task_bounty.wasm Test
cargo test
cargo test -- --nocapture
cargo test test_create_task Deploy
stellar contract deploy
--wasm target/wasm32-unknown-unknown/release/task_bounty.wasm
--source <YOUR_SECRET_KEY>
--network testnet
stellar contract deploy
--wasm target/wasm32-unknown-unknown/release/task_bounty.wasm
--source <YOUR_SECRET_KEY>
--network mainnet
📖 Usage
Creating a Task
// Initialize contract
let contract_id = Address::from_string(&String::from_str(&env, "CONTRACT_ID"));
let client = TaskBountyClient::new(&env, &contract_id);
// Create task with 100 XLM reward client.create_task( &poster, &String::from_str(&env, "Build a DEX interface"), &String::from_str(&env, "Create a React frontend for Stellar DEX"), &token_address, // XLM or token address &1_000_000_000, // 100 XLM (7 decimals) &(env.ledger().timestamp() + 2_592_000), // 30 days &3 // max submissions ); Submitting Work // Submit work with IPFS link client.submit_work( &task_id, &contributor, &String::from_str(&env, "ipfs://QmXxxx..."), &String::from_str(&env, "Completed DEX interface with all features") ); Approving Submissions // Approve and automatically pay contributor client.approve_submission(&task_id, &submission_id, &poster); Handling Disputes // Raise a dispute client.raise_dispute( &task_id, &submission_id, &contributor, &String::from_str(&env, "Work meets all requirements") );
// Resolve dispute (arbitrator) dispute_client.resolve_dispute(&dispute_id, &arbitrator, &true); // true = favor contributor 🔧 How to Extend Add Reputation System // Track contributor reputation pub fn update_reputation(env: &Env, contributor: Address, positive: bool) { let mut rep = get_reputation(env, &contributor); if positive { rep += 1; } else if rep > 0 { rep -= 1; } set_reputation(env, &contributor, rep); } Add Milestone-Based Tasks #[contracttype] pub struct Milestone { pub description: String, pub reward: i128, pub completed: bool, }
pub fn create_milestone_task( env: Env, milestones: Vec ) -> u64 { // Implementation } Add Multi-Token Support Already built-in! TaskBounty supports:
XLM (Stellar's native token) Any Stellar Asset Contract (SAC) token Custom tokens deployed on Stellar Integration with Drips Protocol // Stream rewards over time pub fn stream_reward( env: &Env, contributor: Address, amount: i128, duration: u64 ) { // Configure streaming payment via Drips // Integration with Drips protocol on Stellar } 🧪 Testing Tests cover:
✅ Task creation and escrow ✅ Work submission ✅ Approval and rejection flows ✅ Automatic payouts ✅ Dispute creation and resolution ✅ Edge cases (expired tasks, double submissions, etc.) ✅ Access control ✅ Token transfers Run tests:
cargo test cargo test -- --nocapture # With output 🔒 Security Considerations Authorization: Uses Soroban's built-in require_auth() for access control Fund Safety: Escrow pattern with contract-held funds Deadline Enforcement: Timestamp-based validations Input Validation: Comprehensive checks on all parameters Atomic Operations: Stellar's transaction atomicity guarantees No Reentrancy: Soroban's execution model prevents reentrancy attacks HTTP Security Headers: The Next.js frontend applies CSP, HSTS, and related headers on all responses — see SECURITY_HEADERS.md 📊 Gas Optimization (Fee Efficiency) Stellar advantages:
Fixed low fees: ~0.00001 XLM per operation Predictable costs: No gas price auctions Efficient execution: WebAssembly-based contracts Optimized storage: Compact data structures Typical costs:
Create Task: ~0.0001 XLM Submit Work: ~0.00005 XLM Approve Submission: ~0.0001 XLM Total workflow: < $0.001 USD Compare to Ethereum:
Ethereum: $5-50 per transaction Stellar: $0.0001 per transaction 50,000x cheaper! 🛣️ Roadmap Core task management Work submission and approval Dispute resolution Multi-token support Reputation system Milestone-based tasks Frontend dApp Drips Protocol integration Mobile app DAO governance 🌐 Network Information Testnet Network Passphrase: Test SDF Network ; September 2015 Horizon URL: https://horizon-testnet.stellar.org Friendbot: https://friendbot.stellar.org (get test XLM) Mainnet Network Passphrase: Public Global Stellar Network ; September 2015 Horizon URL: https://horizon.stellar.org 📚 Resources Stellar Documentation Soroban Documentation Stellar CLI Rust Book 📄 License MIT
🤝 Contributing Contributions welcome! Please read CONTRIBUTING.md first.
📞 Contact GitHub: [Your GitHub] Discord: Stellar Discord Twitter: [Your Twitter] Built with ❤️ on Stellar
Issue reference: #83 — Improve Form Accessibility
| Form / Component | Location |
|---|---|
| Waitlist signup | frontend/src/app/(marketing)/landing/components/WaitlistHeroSection.tsx |
| Task filter | frontend/src/components/TaskFilter.tsx |
| Component demo inputs | frontend/src/app/test-components/page.tsx |
| Mobile nav button | frontend/src/components/Navbar.tsx |
Label association — every <input>, <select> (via SelectTrigger), and custom Input component has a matching <label htmlFor={id}> / id= pair. Visually-hidden labels use sr-only where a visible label would break the design (e.g. the waitlist email field uses a sr-only <label> alongside a visible placeholder).
aria-required — required fields (waitlist email) carry aria-required="true".
aria-invalid + aria-describedby — on validation error, aria-invalid="true" is set on the offending input and aria-describedby points to the id of the error message element so screen readers announce the message when focus returns to the field.
Live regions — error/success messages use a persistent aria-live="assertive" container (always in the DOM) rather than conditionally rendered role="alert" elements. This avoids the reliability issue where some screen readers miss announcements from elements that are added to the DOM after page load.
aria-expanded / aria-controls — the mobile navigation toggle already used these correctly; a duplicate-props bug (two className and two sets of aria-* attributes on the same element) was fixed so only the correct values take effect.
All live-region wiring lives directly in each form component. To change the politeness level:
aria-live="assertive"— interrupts the screen reader immediately (used for form errors)aria-live="polite"— waits for the current speech to finish (appropriate for success confirmations)
Change the attribute value in the relevant component file. Do not add role="alert" to empty containers that are always present in the DOM — only use it on elements that are conditionally rendered.
| Test file | What it covers |
|---|---|
frontend/src/app/(marketing)/landing/components/WaitlistHeroSection.test.tsx |
label/id pairing, aria-required, aria-describedby, persistent live region, initial aria-invalid state |
frontend/src/components/TaskFilter.test.tsx |
all 6 for= → id= associations for inputs and selects |