> ## Documentation Index
> Fetch the complete documentation index at: https://doc.playsmart.api.dolly.gg/llms.txt
> Use this file to discover all available pages before exploring further.

# PlaySmart coin economy: earning, converting, and withdrawing

> Understand how PlaySmart's coin economy works: how players earn coins, how coins convert to USD, and how PayPal withdrawals are processed.

PlaySmart runs a two-layer economy: an in-game **coin layer** that rewards players for gameplay, and a **USD money layer** that converts those coins into real money. Coins are lightweight and earned frequently; USD balances accumulate over time as a scheduled conversion job runs in the background. When a player's USD balance is high enough, they can request a PayPal withdrawal. The two layers are separate by design — coins and USD never mix in a single field, so your game logic and your payout logic stay cleanly independent.

## Earning coins

Players earn coins when they complete gameplay events. There are two triggers:

* **`level_complete`** — fires automatically when a `LevelCompleted` event is ingested and `coins_enabled` is `true` for the game. This is the standard earning path for level-based games.
* **`event_trigger`** — fires when a configurable threshold of custom events is reached (controlled by `event_trigger_threshold` in the game's config). This path is disabled by default and suits endless or score-based games.

Every coin award is recorded as an entry in the `coins_transactions` collection. Each transaction captures:

| Field            | Description                                                      |
| ---------------- | ---------------------------------------------------------------- |
| `type`           | `earn` or `deduct`                                               |
| `trigger`        | `level_complete` or `event_trigger`                              |
| `amount`         | Coins awarded or removed                                         |
| `balance_before` | The player's `coins_balance` immediately before this transaction |
| `balance_after`  | The player's `coins_balance` immediately after this transaction  |

<Note>
  Coin transactions are an **immutable ledger**. Once written, they cannot be updated or deleted. Any adjustment requires a new `deduct` transaction.
</Note>

The player's current coin balance is stored in the `coins_balance` field on their profile.

### Coin reward modes

Each game has a `reward_mode` that controls how the coin amount is calculated per award:

* **`fixed`** — every award grants exactly `reward_fixed_amount` coins.
* **`random_range`** — the amount is drawn randomly between `reward_min_amount` and `reward_max_amount`.

You can also tune rewards per trigger using multipliers: `level_completion_multiplier` (default `1.0`) and `event_trigger_multiplier` (default `0.5`).

## Converting coins to USD

A scheduled conversion job runs on an interval defined by `conversion_interval_hours` in the active conversion setting. When the job runs, it selects users whose `next_conversion_at` timestamp is in the past and whose `coins_balance` is above zero.

There are two conversion modes, set per conversion setting:

* **`standard`** — a fixed coin-to-USD ratio (`standard_coin_to_usd_ratio`) is applied. For example, a ratio of `0.00001` means 1,000 coins convert to \$0.01.
* **`elastic`** — the payout is derived from the user's actual ad revenue, multiplied by `ads_revenue_share`. This mode ties payouts directly to the ad income a player generates.

If the calculated USD amount is below `min_payout_usd`, the conversion is skipped for that run and the user's coins remain pending until the next interval.

Each successful conversion is recorded in the `money_conversions` collection with:

| Field              | Description                                     |
| ------------------ | ----------------------------------------------- |
| `coins_converted`  | Number of coins consumed                        |
| `usd_amount`       | USD credited to the player                      |
| `mode`             | `standard` or `elastic`                         |
| `ads_revenue_used` | Ads revenue factored into an elastic conversion |

<Note>
  Money conversions are also an **immutable ledger**. Once created, they cannot be modified.
</Note>

When a conversion succeeds, the player's `coins_balance` decreases and their `money_balance_usd` increases by the converted amount. The profile is also updated with `last_conversion_at` and `next_conversion_at`.

## Requesting a withdrawal

Once a player has a `money_balance_usd` above the configured minimum, they can request a PayPal withdrawal. Withdrawal behaviour is governed by two app-level settings:

* **`minimum_withdrawal_amount`** — the minimum USD amount required to submit a request.
* **`withdraw_cooldown_hours`** — how many hours must pass between two withdrawal requests from the same player.

When a withdrawal is created:

* The `amount_usd` is immediately debited from the player's `money_balance_usd`.
* The withdrawal record is created with `status: "pending"`.
* The player provides their `paypal_email` to receive the payment.

An admin then reviews the request and marks it `paid` or `failed`.

The currently supported `payment_method` values are:

```
paypal
```

## End-to-end flow

<Steps>
  <Step title="Player completes a level">
    Your game sends a `LevelCompleted` event to `POST /ingest/events`. If `coins_enabled` is `true` for the game, the event processor fires a coin award.
  </Step>

  <Step title="Coin transaction is recorded">
    A `coins_transactions` entry is written with `type: "earn"` and `trigger: "level_complete"`. The player's `coins_balance` increases.
  </Step>

  <Step title="Conversion job runs">
    On the next scheduled interval, the conversion job processes the player. Coins are converted to USD using the active conversion setting's mode and rate. A `money_conversions` record is written and `money_balance_usd` increases.
  </Step>

  <Step title="Player requests a withdrawal">
    When the player's `money_balance_usd` meets the minimum, they call `POST /v1/withdrawals` with their PayPal email and the amount they want to withdraw.
  </Step>

  <Step title="Admin processes the payout">
    An admin reviews the withdrawal in the dashboard and marks it `paid` (triggering the PayPal transfer) or `failed` (which refunds the balance back to the player).
  </Step>
</Steps>

<Warning>
  A withdrawal's `amount_usd` is debited from `money_balance_usd` at the moment the request is created — before the admin approves it. If the request is later marked `failed`, the balance is restored automatically.
</Warning>
