AiToEarn Rewards Mechanism: Technical Implementation of the Credit-Based System

AiToEarn implements a credit-based reward system where users earn platform credits (stored in cents) upon completing monetization tasks, with all transactions recorded in MongoDB for auditability and automatic expiration handling.

The AiToEarn platform incentivizes user participation through a sophisticated reward infrastructure built on NestJS and MongoDB. Unlike simple point systems, this implementation treats rewards as monetary credits with full audit trails, expiration management, and transactional safety guarantees.

Core Architecture of the AiToEarn Rewards Mechanism

Task Schema and Reward Definitions

Each monetization opportunity begins with the Task schema defined in project/aitoearn-electron/server/src/db/schema/task.schema.ts. This schema includes a reward: number field that stores the monetary value in cents. When a task reaches REWARDED status, the system creates a corresponding UserTask entry containing the reward amount and timestamp (rewardTime) in project/aitoearn-electron/server/src/modules/task/task.controller.ts.

The Credit Ledger Pattern

The platform implements a double-entry style ledger through CreditsHelperService located at project/aitoearn-backend/libs/helpers/src/credits/credits-helper.service.ts. This service maintains two critical data structures: the user's aggregate balance and individual CreditsRecord documents that provide immutable transaction history for every credit movement.

How Rewards Are Issued and Recorded

From Task Completion to Credit Balance

When users complete tasks, the flow traverses from project/aitoearn-electron/server/src/modules/task/task.controller.ts to the credits system. The controller marks tasks as rewarded, triggering the credit issuance workflow that atomically updates the user's balance through the helper service.

Transaction Safety with @Transactional

All credit modifications use NestJS @Transactional decorators to ensure ACID compliance across MongoDB operations. If a reward issuance fails midway, the entire operation rolls back, preventing partial credit updates or orphaned CreditsRecord documents.

Managing Credit Lifecycles

Adding Credits via CreditsHelperService

The addCredits() method in CreditsHelperService handles reward distribution. It accepts an AddCreditsDto containing the user ID, amount in cents, credit type (typically CreditsType.Reward), description, and metadata for auditing.

import { CreditsHelperService } from '@yikart/helpers';
import { AddCreditsDto, CreditsType } from '@yikart/helpers';

async function grantReward(userId: string, amountCents: number) {
  const dto: AddCreditsDto = {
    userId,
    amount: amountCents,
    type: CreditsType.Reward,
    description: 'Task reward',
    metadata: { source: 'task-123' },
  };
  await creditsHelper.addCredits(dto);
}

Deducting Credits for Task Execution

When users execute paid tasks, deductCredits() validates available balance, checks expiration dates, and decrements the appropriate credit records. This method supports complex scenarios like partial credit consumption from multiple buckets using FIFO (First-In-First-Out) selection logic.

import { CreditsHelperService } from '@yikart/helpers';
import { DeductCreditsDto, CreditsType } from '@yikart/helpers';

async function consumeCredits(userId: string, costCents: number) {
  const dto: DeductCreditsDto = {
    userId,
    amount: costCents,
    type: CreditsType.TaskExecution,
    description: 'Start task XYZ',
    metadata: { taskId: 'xyz' },
  };
  await creditsHelper.deductCredits(dto);
}

Automatic Expiration Handling

Credits can include an optional expiredAt timestamp. A nightly job (checkUserCreditsExpiration) scans for expired records, zeros out the balances, and logs separate expired records. This prevents stale promotional credits from remaining in circulation while maintaining complete audit trails.

Frontend Integration

Displaying Rewards with RewardDisplay

The React frontend consumes reward data through the RewardDisplay component documented in project/aitoearn-web/src/components/README.md. This component intelligently selects the proper pricing model (CPS, CPM, or CPE) and renders the reward amount across three size variants: sidebar, card, and compact.

Querying User Balances

CreditsService at project/aitoearn-backend/apps/aitoearn-server/src/core/credits/credits.service.ts exposes REST endpoints consumed by the frontend wrapper in project/aitoearn-web/src/api/credits.ts:

import { http } from '@/utils/http';

export function getCreditsBalance() {
  return http.get<{ balance: number }>('user/credits');
}

// Usage
const { balance } = await getCreditsBalance();
console.log(`Current balance: $${(balance / 100).toFixed(2)}`);

Summary

  • Cent-based precision: AiToEarn stores all rewards as integers (cents) to eliminate floating-point errors common in financial calculations.
  • Double-entry accounting: Every credit movement generates immutable CreditsRecord documents for complete audit trails.
  • Transaction safety: NestJS @Transactional decorators ensure atomic updates across task status and credit balance collections.
  • Expiration management: Automated nightly jobs handle time-limited credits without manual intervention.
  • Modular separation: Credit logic (CreditsHelperService) remains separate from API exposure (CreditsService) and frontend components.

Frequently Asked Questions

What currency format does AiToEarn use for rewards?

AiToEarn stores all monetary values as integers representing cents (e.g., $1.00 = 100). This approach eliminates floating-point precision errors common in JavaScript financial calculations and simplifies MongoDB aggregation pipelines for reporting.

How does AiToEarn prevent duplicate reward issuance?

The platform leverages transactional database operations using NestJS @Transactional decorators. When a task status changes to REWARDED in task.controller.ts, the credit creation and task update occur within a single atomic transaction, ensuring either both succeed or both fail, preventing double-payment scenarios.

Can credits expire in the AiToEarn system?

Yes, the CreditsHelperService supports optional expiration through the expiredAt timestamp field. A scheduled nightly job (checkUserCreditsExpiration) scans for expired records, zeros out usable balances, and creates corresponding audit entries to maintain ledger integrity and prevent stale currency usage.

How does the frontend determine which pricing model to display?

The RewardDisplay component automatically selects between CPS (Cost Per Sale), CPM (Cost Per Mille), and CPE (Cost Per Engagement) models based on the task's pricingModel property passed from the backend. It renders the reward amount appropriately for three distinct layouts: sidebar navigation, task cards, and compact list views.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →