This document covers every public method, type, error, and event in the TaskBounty Soroban smart contract.
pub struct Task {
pub id: u64, // Auto-assigned task ID (starts at 1)
pub poster: Address, // Account that created the task
pub title: String, // Short task title
pub description: String, // Full task requirements
pub token: Address, // Token contract address used for reward
pub reward: i128, // Reward amount in token's smallest unit (7 decimals for XLM)
pub deadline: u64, // Unix timestamp — task expires after this
pub max_submissions: u32, // Maximum concurrent submissions allowed
pub submission_count: u32, // Number of submissions received so far
pub status: TaskStatus, // Current task state
pub created_at: u64, // Unix timestamp when task was created
}pub struct Submission {
pub id: u64, // Auto-assigned submission ID (starts at 1)
pub task_id: u64, // ID of the task this submission is for
pub contributor: Address, // Account that submitted the work
pub work_url: String, // URL to the work (IPFS, Arweave, GitHub, etc.)
pub description: String, // Description of the work done
pub submitted_at: u64, // Unix timestamp when submitted
pub status: SubmissionStatus, // Current submission state
}pub struct Dispute {
pub id: u64, // Auto-assigned dispute ID
pub task_id: u64, // ID of the disputed task
pub submission_id: u64, // ID of the disputed submission
pub raiser: Address, // Account that raised the dispute (poster or contributor)
pub reason: String, // Reason for the dispute
pub created_at: u64, // Unix timestamp when dispute was created
}pub enum TaskStatus {
Open, // Task created, accepting submissions
InProgress, // At least one submission received
Completed, // A submission was approved and reward paid out
Cancelled, // Task cancelled by poster, reward refunded
Disputed, // An active dispute is open on this task
}State transitions:
Open → InProgress (first submit_work)
InProgress → Completed (approve_submission)
InProgress → Cancelled (cancel_task, if no approved submissions)
Open / InProgress → Disputed (raise_dispute)
Open / InProgress → Cancelled (cancel_task)
pub enum SubmissionStatus {
Pending, // Awaiting review by poster
Approved, // Approved; reward transferred to contributor
Rejected, // Rejected by poster
}All errors are returned as u32 panic codes via panic_with_error!.
| Code | Name | When thrown |
|---|---|---|
| 1 | TaskNotFound |
Task ID does not exist |
| 2 | SubmissionNotFound |
Submission ID does not exist |
| 3 | Unauthorized |
Caller is not the expected account (e.g. poster) |
| 4 | TaskExpired |
Task deadline has passed |
| 5 | InvalidTaskStatus |
Operation not allowed in current task state |
| 6 | InvalidSubmissionStatus |
Operation not allowed in current submission state |
| 7 | InsufficientReward |
Reward below minimum (0.1 XLM / 1,000,000 stroops) |
| 8 | InvalidDeadline |
Deadline is in the past or more than 365 days away |
| 9 | InvalidMaxSubmissions |
max_submissions is 0 |
| 10 | AlreadySubmitted |
Contributor already submitted to this task |
| 11 | MaxSubmissionsReached |
Task has reached its submission cap |
| 12 | PaymentFailed |
Token transfer failed |
| 13 | DisputeAlreadyExists |
A dispute already exists for this submission |
One-time setup. Must be called once after deployment before any other function.
pub fn initialize(env: Env, dispute_resolver: Address, admin: Address)Parameters:
| Name | Type | Description |
|---|---|---|
dispute_resolver |
Address |
Address of the DisputeResolver contract |
admin |
Address |
Address of the contract administrator |
Auth required: None (one-time setup)
Errors: Panics if called more than once.
Creates a new task and escrows the reward into the contract.
pub fn create_task(
env: Env,
poster: Address,
title: String,
description: String,
token: Address,
reward: i128,
deadline: u64,
max_submissions: u32,
) -> u64Parameters:
| Name | Type | Constraints | Description |
|---|---|---|---|
poster |
Address |
Must sign the tx | Task creator |
title |
String |
— | Short task title |
description |
String |
— | Full task requirements |
token |
Address |
Valid SAC or XLM token | Token used for reward |
reward |
i128 |
≥ 1,000,000 (0.1 XLM) | Amount in token's smallest unit |
deadline |
u64 |
now < deadline ≤ now + 365 days |
Unix timestamp |
max_submissions |
u32 |
≥ 1 | Max concurrent submissions |
Returns: u64 — the new task ID.
Auth required: poster.require_auth()
Side effects:
- Transfers
rewardfromposterto contract (escrow) - Emits
TaskCreatedevent
Errors: InsufficientReward, InvalidDeadline, InvalidMaxSubmissions
Submits work for a task. Each contributor may submit once per task.
pub fn submit_work(
env: Env,
task_id: u64,
contributor: Address,
work_url: String,
description: String,
) -> u64Parameters:
| Name | Type | Description |
|---|---|---|
task_id |
u64 |
ID of the target task |
contributor |
Address |
Account submitting the work |
work_url |
String |
URL to the work (IPFS, Arweave, GitHub, etc.) |
description |
String |
Description of what was done |
Returns: u64 — the new submission ID.
Auth required: contributor.require_auth()
Side effects:
- Moves task status from
Open→InProgresson first submission - Increments
task.submission_count - Emits
WorkSubmittedevent
Errors: TaskNotFound, InvalidTaskStatus, TaskExpired, AlreadySubmitted, MaxSubmissionsReached
Approves a pending submission and immediately transfers the reward to the contributor.
pub fn approve_submission(
env: Env,
task_id: u64,
submission_id: u64,
poster: Address,
)Parameters:
| Name | Type | Description |
|---|---|---|
task_id |
u64 |
ID of the task |
submission_id |
u64 |
ID of the submission to approve |
poster |
Address |
Task poster (must match task.poster) |
Auth required: poster.require_auth()
Side effects:
- Sets submission status →
Approved - Sets task status →
Completed - Transfers
task.rewardfrom contract tosubmission.contributor - Emits
SubmissionApprovedevent
Errors: TaskNotFound, SubmissionNotFound, Unauthorized, InvalidSubmissionStatus, InvalidTaskStatus
Note: Cannot approve if there is an active dispute on that submission.
Rejects a pending submission. The task stays open for further submissions.
pub fn reject_submission(
env: Env,
task_id: u64,
submission_id: u64,
poster: Address,
reason: String,
)Parameters:
| Name | Type | Description |
|---|---|---|
task_id |
u64 |
ID of the task |
submission_id |
u64 |
ID of the submission to reject |
poster |
Address |
Task poster (must match task.poster) |
reason |
String |
Reason for rejection |
Auth required: poster.require_auth()
Side effects:
- Sets submission status →
Rejected - Emits
SubmissionRejectedevent
Errors: TaskNotFound, SubmissionNotFound, Unauthorized, InvalidSubmissionStatus
Cancels a task and refunds the escrowed reward to the poster.
pub fn cancel_task(env: Env, task_id: u64, poster: Address)Parameters:
| Name | Type | Description |
|---|---|---|
task_id |
u64 |
ID of the task to cancel |
poster |
Address |
Task poster (must match task.poster) |
Auth required: poster.require_auth()
Side effects:
- Sets task status →
Cancelled - Transfers
task.rewardfrom contract back toposter - Emits
TaskCancelledevent
Errors: TaskNotFound, Unauthorized, InvalidTaskStatus
Note: Cannot cancel a task that is already Completed or Cancelled. Also blocked if any submission has been approved.
Raises a dispute on a pending or rejected submission. Either the poster or contributor may raise a dispute.
pub fn raise_dispute(
env: Env,
task_id: u64,
submission_id: u64,
raiser: Address,
reason: String,
)Parameters:
| Name | Type | Description |
|---|---|---|
task_id |
u64 |
ID of the task |
submission_id |
u64 |
ID of the submission in dispute |
raiser |
Address |
Account raising the dispute (poster or contributor) |
reason |
String |
Reason for the dispute |
Auth required: raiser.require_auth()
Side effects:
- Sets task status →
Disputed - Creates and stores a
Disputerecord - Emits
DisputeRaisedevent
Errors: TaskNotFound, SubmissionNotFound, Unauthorized, InvalidSubmissionStatus, DisputeAlreadyExists
Returns full task details.
pub fn get_task(env: Env, task_id: u64) -> TaskParameters:
| Name | Type | Description |
|---|---|---|
task_id |
u64 |
ID of the task |
Returns: Task struct.
Errors: Panics with TaskNotFound if not found.
Returns full submission details.
pub fn get_submission(env: Env, submission_id: u64) -> SubmissionParameters:
| Name | Type | Description |
|---|---|---|
submission_id |
u64 |
ID of the submission |
Returns: Submission struct.
Errors: Panics with SubmissionNotFound if not found.
Returns all submission IDs for a given task.
pub fn get_task_submissions(env: Env, task_id: u64) -> Vec<u64>Parameters:
| Name | Type | Description |
|---|---|---|
task_id |
u64 |
ID of the task |
Returns: Vec<u64> — list of submission IDs. Empty if no submissions.
Returns the total number of tasks ever created.
pub fn get_total_tasks(env: Env) -> u64Returns: u64 — task counter value.
Returns the total number of submissions ever created.
pub fn get_total_submissions(env: Env) -> u64Returns: u64 — submission counter value.
Checks whether a contributor has already submitted to a given task.
pub fn has_submitted(env: Env, task_id: u64, contributor: Address) -> boolParameters:
| Name | Type | Description |
|---|---|---|
task_id |
u64 |
ID of the task |
contributor |
Address |
Address to check |
Returns: bool — true if contributor has an existing submission for this task.
All events are emitted with two-symbol topics and a tuple payload.
Emitted when a new task is created.
- Topics:
("task", "created") - Data:
(task_id: u64, poster: Address, title: String, reward: i128, deadline: u64)
Emitted when a contributor submits work.
- Topics:
("work", "submit") - Data:
(task_id: u64, submission_id: u64, contributor: Address, work_url: String)
Emitted when a submission is approved and reward is paid.
- Topics:
("sub", "approved") - Data:
(task_id: u64, submission_id: u64, contributor: Address, reward: i128)
Emitted when a submission is rejected.
- Topics:
("sub", "rejected") - Data:
(task_id: u64, submission_id: u64, contributor: Address)
Emitted when a task is cancelled and reward refunded.
- Topics:
("task", "cancel") - Data:
(task_id: u64, poster: Address)
Emitted when a dispute is opened.
- Topics:
("dispute", "raised") - Data:
(task_id: u64, submission_id: u64, raiser: Address, reason: String)
Replace <CONTRACT_ID>, <KEY>, <ADDRESS> with your values.
# Initialize
stellar contract invoke --id <CONTRACT_ID> --source <KEY> --network testnet \
-- initialize \
--dispute_resolver <DISPUTE_RESOLVER_ADDRESS> \
--admin <ADMIN_ADDRESS>
# Create a task (100 XLM = 1_000_000_000 stroops, deadline ~30 days from now)
stellar contract invoke --id <CONTRACT_ID> --source <KEY> --network testnet \
-- create_task \
--poster <POSTER_ADDRESS> \
--title "Build a DEX interface" \
--description "Create a React frontend for Stellar DEX with swap UI" \
--token CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC \
--reward 1000000000 \
--deadline 1780000000 \
--max_submissions 3
# Submit work
stellar contract invoke --id <CONTRACT_ID> --source <KEY> --network testnet \
-- submit_work \
--task_id 1 \
--contributor <CONTRIBUTOR_ADDRESS> \
--work_url "ipfs://QmXxxx..." \
--description "Completed DEX UI with all required swap features"
# Approve submission
stellar contract invoke --id <CONTRACT_ID> --source <KEY> --network testnet \
-- approve_submission \
--task_id 1 \
--submission_id 1 \
--poster <POSTER_ADDRESS>
# Reject submission
stellar contract invoke --id <CONTRACT_ID> --source <KEY> --network testnet \
-- reject_submission \
--task_id 1 \
--submission_id 1 \
--poster <POSTER_ADDRESS> \
--reason "Missing mobile responsive layout"
# Cancel task
stellar contract invoke --id <CONTRACT_ID> --source <KEY> --network testnet \
-- cancel_task \
--task_id 1 \
--poster <POSTER_ADDRESS>
# Raise dispute
stellar contract invoke --id <CONTRACT_ID> --source <KEY> --network testnet \
-- raise_dispute \
--task_id 1 \
--submission_id 1 \
--raiser <CONTRIBUTOR_ADDRESS> \
--reason "Work meets all stated requirements, rejection was unjustified"
# Read task
stellar contract invoke --id <CONTRACT_ID> --source <KEY> --network testnet \
-- get_task --task_id 1
# Read submission
stellar contract invoke --id <CONTRACT_ID> --source <KEY> --network testnet \
-- get_submission --submission_id 1
# List submission IDs for a task
stellar contract invoke --id <CONTRACT_ID> --source <KEY> --network testnet \
-- get_task_submissions --task_id 1
# Check if contributor has submitted
stellar contract invoke --id <CONTRACT_ID> --source <KEY> --network testnet \
-- has_submitted --task_id 1 --contributor <CONTRIBUTOR_ADDRESS>use soroban_sdk::{Address, Env, String};
let client = TaskBountyContractClient::new(&env, &contract_id);
// Initialize
client.initialize(&dispute_resolver, &admin);
// Create a task — reward is in stroops (1 XLM = 10_000_000 stroops)
let task_id = client.create_task(
&poster,
&String::from_str(&env, "Build DEX interface"),
&String::from_str(&env, "Create a React frontend for Stellar DEX"),
&xlm_token,
&1_000_000_000_i128, // 100 XLM
&(env.ledger().timestamp() + 2_592_000), // 30 days
&3_u32,
);
// Submit work
let submission_id = client.submit_work(
&task_id,
&contributor,
&String::from_str(&env, "ipfs://QmXxxx..."),
&String::from_str(&env, "Completed all requirements"),
);
// Approve and pay
client.approve_submission(&task_id, &submission_id, &poster);
// Reject
client.reject_submission(
&task_id,
&submission_id,
&poster,
&String::from_str(&env, "Needs responsive design fixes"),
);
// Cancel
client.cancel_task(&task_id, &poster);
// Dispute
client.raise_dispute(
&task_id,
&submission_id,
&contributor,
&String::from_str(&env, "Work fully meets requirements"),
);
// Read
let task = client.get_task(&task_id);
let submission = client.get_submission(&submission_id);
let submissions: Vec<u64> = client.get_task_submissions(&task_id);
let total = client.get_total_tasks();
let already_submitted: bool = client.has_submitted(&task_id, &contributor);Stellar tokens use 7 decimal places. To express an amount in "whole" tokens, multiply by 10_000_000.
| Human amount | Stroop value |
|---|---|
| 0.1 XLM (minimum reward) | 1_000_000 |
| 1 XLM | 10_000_000 |
| 100 XLM | 1_000_000_000 |
| 1,000 XLM | 10_000_000_000 |