Build a full task marketplace — a web app where merchants or users create bounties (data labeling, product reviews, content creation), solvers claim and complete them, and settlement flows through Podium’s task pool contracts.
What You’ll Build
Prerequisites
npm install @podium-sdk/node-sdk
import { createPodiumClient } from '@podium-sdk/node-sdk';
const client = createPodiumClient({
apiKey: process.env.PODIUM_API_KEY,
});
Step 1: Create a Task Pool
Before creating tasks, provision a task pool. This deploys the on-chain escrow contract.
async function createTaskPool(params: {
name: string;
description: string;
totalBudget: number;
maxSolvers: number;
}) {
const pool = await client.tasks.createTaskPools({
requestBody: {
name: params.name,
description: params.description,
totalBudget: params.totalBudget,
maxSolvers: params.maxSolvers,
},
});
return pool;
}
Step 2: Create Individual Tasks
Each task within a pool has its own reward amount and requirements.
async function createTask(poolId: string, params: {
title: string;
description: string;
reward: number;
deadline: string;
requirements: string[];
}) {
const task = await client.tasks.createTasks({
requestBody: {
poolId,
title: params.title,
description: params.description,
reward: params.reward,
deadline: params.deadline,
requirements: params.requirements,
},
});
return task;
}
// Create a batch of bounties
const pool = await createTaskPool({
name: 'Q1 Product Reviews',
description: 'Honest product reviews with photos',
totalBudget: 5000,
maxSolvers: 100,
});
await createTask(pool.id, {
title: 'Review: CeraVe Moisturizing Cream',
description: 'Write 200+ word review with before/after photos. Must show 2+ weeks of usage.',
reward: 50,
deadline: '2026-04-01T00:00:00Z',
requirements: ['200+ words', 'Photos required', '2-week usage minimum'],
});
Step 3: Public Task Feed
Build the browsing experience for solvers. List open tasks with filters.
async function listOpenTasks(filters?: {
poolId?: string;
minReward?: number;
status?: string;
}) {
const tasks = await client.tasks.listTasks();
let filtered = tasks.tasks ?? [];
if (filters?.poolId) {
filtered = filtered.filter((t: any) => t.poolId === filters.poolId);
}
if (filters?.minReward) {
filtered = filtered.filter((t: any) => t.reward >= filters.minReward);
}
if (filters?.status) {
filtered = filtered.filter((t: any) => t.status === filters.status);
}
return filtered;
}
Task Card Component
function TaskCard({ task, onClaim }: { task: any; onClaim: (id: string) => void }) {
return (
<div className="rounded-xl border p-5">
<div className="flex items-start justify-between">
<div>
<h3 className="font-semibold">{task.title}</h3>
<p className="mt-1 text-sm text-gray-500">{task.description}</p>
</div>
<span className="rounded-full bg-green-100 px-3 py-1 text-sm font-medium text-green-800">
${task.reward}
</span>
</div>
<div className="mt-3 flex flex-wrap gap-2">
{task.requirements?.map((req: string, i: number) => (
<span key={i} className="rounded-md bg-gray-100 px-2 py-1 text-xs">
{req}
</span>
))}
</div>
<div className="mt-4 flex items-center justify-between">
<span className="text-sm text-gray-400">
Due {new Date(task.deadline).toLocaleDateString()}
</span>
{task.status === 'OPEN' && (
<button
onClick={() => onClaim(task.id)}
className="rounded-lg bg-indigo-600 px-4 py-2 text-sm text-white"
>
Claim Bounty
</button>
)}
</div>
</div>
);
}
Step 4: Solver Claim Flow
When a solver claims a task, they commit to completing it. Podium’s task pool tracks the assignment.
async function claimTask(taskId: string) {
const response = await fetch(
`${process.env.PODIUM_BASE_URL}/api/v1/task-pools/${taskId}/claim`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.PODIUM_API_KEY}`,
'Content-Type': 'application/json',
},
}
);
if (!response.ok) throw new Error('Failed to claim task');
return response.json();
}
Solver-specific routes (claim, submit, verify) may not be available in the generated SDK yet. Use direct HTTP calls for these endpoints as shown above.
Step 5: Submit and Verify
After completing the work, the solver submits evidence. The task creator then verifies and approves settlement.
async function submitWork(taskId: string, submission: {
content: string;
proofUrl: string;
metadata?: Record<string, string>;
}) {
const response = await fetch(
`${process.env.PODIUM_BASE_URL}/api/v1/task-pools/${taskId}/submit`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.PODIUM_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(submission),
}
);
return response.json();
}
async function verifyAndSettle(taskId: string, approved: boolean) {
const response = await fetch(
`${process.env.PODIUM_BASE_URL}/api/v1/task-pools/${taskId}/verify`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.PODIUM_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ approved }),
}
);
return response.json();
}
Step 6: Settlement Dashboard
Track the lifecycle of all tasks in a pool.
function PoolDashboard({ poolId }: { poolId: string }) {
const { data: tasks } = useQuery({
queryKey: ['pool-tasks', poolId],
queryFn: () => listOpenTasks({ poolId }),
});
const stats = {
open: tasks?.filter((t: any) => t.status === 'OPEN').length ?? 0,
claimed: tasks?.filter((t: any) => t.status === 'CLAIMED').length ?? 0,
submitted: tasks?.filter((t: any) => t.status === 'SUBMITTED').length ?? 0,
settled: tasks?.filter((t: any) => t.status === 'SETTLED').length ?? 0,
};
return (
<div>
<div className="grid grid-cols-4 gap-4">
<StatCard label="Open" value={stats.open} color="blue" />
<StatCard label="In Progress" value={stats.claimed} color="yellow" />
<StatCard label="Review" value={stats.submitted} color="purple" />
<StatCard label="Settled" value={stats.settled} color="green" />
</div>
<div className="mt-6 space-y-4">
{tasks?.map((task: any) => (
<TaskCard key={task.id} task={task} onClaim={() => {}} />
))}
</div>
</div>
);
}
On-Chain Settlement
Under the hood, when a task is verified, the TaskPool smart contract releases USDC from escrow to the solver’s wallet. The settlement is fully on-chain.
For more on the contract internals, see the TaskPool Contract Reference.