Crowdmint is a Solana-based crowdsourcing app where users fund tasks and workers earn rewards for submissions. This README documents the system design for the deposit (task funding) and withdrawal (worker payout) flows based on the current codebase.
Goal: A task creator funds a task by transferring SOL to the platform wallet, then registers the task on-chain proof and metadata in the database.
sequenceDiagram
participant UserWallet as User Wallet
participant WebClient as Web Client
participant API as API (Next.js)
participant Solana as Solana Devnet
participant DB as Postgres (Prisma)
WebClient->>UserWallet: Request SOL transfer to PARENT_WALLET_ADDRESS
UserWallet->>Solana: Submit transfer transaction
Solana-->>WebClient: Confirmed signature
WebClient->>API: POST /api/user/tasks (task + signature)
API->>Solana: Fetch tx by signature
API->>API: Validate sender, recipient, amount
API->>DB: Create Task + Options
DB-->>API: Task created
API-->>WebClient: 200 OK (taskId)
Primary flow (client + API):
- User wallet transfer (client): The task creator sends SOL from their wallet to the platform wallet (
PARENT_WALLET_ADDRESS).- Client code:
src/components/user/Upload.tsx
- Client code:
- Confirm transaction (client): The client waits for the transaction confirmation and captures the transaction signature.
- Create task (API): The client submits task details + the signature to the API.
- API:
POST /api/user/tasksinsrc/app/api/user/tasks/route.ts
- API:
- Server-side validation (API):
- Fetches the transaction from Solana devnet and verifies:
- Amount matches the submitted task amount.
- Recipient is the platform wallet (
PARENT_WALLET_ADDRESS). - Sender matches the user’s registered wallet address.
- Fetches the transaction from Solana devnet and verifies:
- Database write (API):
- Creates a
Taskrecord with amount stored in lamports. - Creates
Optionrecords for the uploaded images.
- Creates a
Key invariants:
- Tasks are only created if the on-chain transfer is valid for amount, destination, and sender.
- Amount is stored in lamports for consistency (
LAMPORTS_PER_SOL).
Goal: Workers accumulate pending earnings from submissions and can request payouts. Payout requests are queued and processed asynchronously, one by one, using a Lambda worker that signs transfers via AWS KMS.
sequenceDiagram
participant Worker as Worker
participant WebClient as Web Client
participant API as API (Next.js)
participant DB as Postgres (Prisma)
participant SQS as SQS FIFO
participant Lambda as Payout Lambda
participant Cron as Reconciler Cron
participant KMS as AWS KMS
participant Solana as Solana Devnet
Worker->>WebClient: Request payout
WebClient->>API: POST /api/worker/payouts
API->>DB: Move pendingBalance -> lockedBalance
API->>DB: Create Payouts(status=PROCESSING)
API->>SQS: Enqueue payout message (workerId group)
SQS-->>Lambda: Trigger payout processor
Lambda->>KMS: Decrypt/sign with private key
Lambda->>Solana: Send SOL to worker address
Lambda->>DB: Update payout status + balances
Cron->>DB: Find pending/processing payouts
Cron->>Solana: Check signatures / confirmations
Cron->>DB: Reconcile status and balances
On-chain earnings accumulation (API):
- Worker submits a task (API):
- API:
POST /api/worker/submissioninsrc/app/api/worker/submission/route.ts
- API:
- Pending balance increments (DB):
- Each submission credits the worker’s
pendingBalancebytask.amount / task.maximumSubmissions.
- Each submission credits the worker’s
Payout request + queueing (API):
- Worker requests payout (API):
- API:
POST /api/worker/payoutsinsrc/app/api/worker/payouts/route.ts
- API:
- Atomic balance move + payout record (DB transaction):
- Move
pendingBalance -> lockedBalance - Create a
Payoutsrecord withstatus = PROCESSING
- Move
- Queue message (SQS FIFO):
- Send a message to the payout queue (
SQS_URL) with:payoutId,workerId,workerAddress,amount
- FIFO queue ensures per-worker ordering via
MessageGroupId = workerId.
- Send a message to the payout queue (
Async payout execution (Lambda worker):
- Queue-triggered Lambda consumes messages one by one.
- Lambda retrieves the private key from AWS KMS.
- Lambda signs and broadcasts the SOL transfer from the platform wallet to the worker’s address.
- Lambda updates payout status (
SUCCESS/FAILED) and clearslockedBalanceappropriately.- Note: This Lambda code is not in this repo; it is assumed to be deployed separately.
Reconciliation cron (external):
- Cron scans pending/processing payouts and checks on-chain confirmations.
- Cron updates payout statuses and ensures balances match on-chain reality.
- Note: This cron job is not in this repo; it is assumed to be deployed separately.
Task: funded tasks (stores signature, amount in lamports, and options)Submission: worker task submissions (records earned amount)Worker: maintainspendingBalanceandlockedBalancePayouts: payout requests and status
Schema: prisma/schema.prisma
- Deposit / Task funding
POST /api/user/tasks→ create a funded task after verifying on-chain transfer
- Worker earnings
POST /api/worker/submission→ record submission and incrementpendingBalance
- Withdrawal / Payout
POST /api/worker/payouts→ lock funds and enqueue payout message
- Queueing: FIFO SQS ensures sequential payout processing per worker.
- Signing: The withdrawal signer key is secured in AWS KMS, not in the app.
- Reconciliation: A scheduled cron job confirms pending transactions and reconciles payout status.
- Idempotency: Queue messages are deduplicated using
payout.id + timestamp. - Environment variables:
REGION,ACCESS_KEY,SECRET_KEY,SQS_URLare required for payout queueing.
npm run dev