# Economies - UGC

Welcome to the **UGC layer** of Sunflower Land. This is not only cosmetics: players can launch **resources**, **complex gameplay loops**, and more through the **resource launchpad** and **economy editor**.

* This feature is **experimental**. Enable it in the game: **Settings → Developer Options → Experiments → Economy Editor**.
* With the economy editor enabled, you can also browse other players’ economies and games from **Marketplace → Economies**.

This document focuses on **HTTP APIs** used by hosted HTML minigames. Handler code in this repo lives under `src/api/minigames/` (session + actions) and is wired in `infra/api.ts` as the **Minigames** API Gateway (see `MinigamesApi` routes for `/data`, `/action`, `/animate/...`, `/bumpkins/metadata/...`).

***

### Setting up an economy

An **economy** is your tokenised design: resources, rules, and copy that define what players can hold and do. You can keep it minimal or build something closer to a **world builder**.

#### Items

Here you define the things that make up your game: resources, buildings, achievements, skills, scores, etc. Think in terms of **tokenising everything**—each distinct thing players earn, spend, or unlock should usually be an **item** (balance token key) with metadata (name, description, optional art, marketplace fields).

#### Rules

Rules describe **allowed transitions** on player balances (and timed generator jobs).

| Rule type (editor) | Purpose                                                                                                                                                                                                                                                              |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Shop**           | **Burn** some tokens, **mint** others (and optional **require** / caps). Use for crafting, shops, exchanges (e.g. burn 3 wood, mint an axe). Shop-derived rows can appear in the default economy dashboard when `showInShop` is not `false`.                         |
| **Generator**      | Like a shop, but includes **time** and optional **random** collect outcomes (`chance` on collect rows). Use for cooking, production, staking-style “come back later” loops. Often modelled as one action with `produce` + `collect` and `itemId` for the active job. |
| **Custom**         | Rules for **advanced / iframe** minigames. They are **not** shown in the basic economy dashboard; your game calls them by **action id** via the API.                                                                                                                 |

Every time you **save**, your economy config is published for players. For behaviour that cannot be expressed in rules alone (dungeons, battles, etc.), host a **custom minigame** and call **custom** actions from your client.

Server-side rule shapes and processing align with `src/domain/playerEconomy/types.ts` and `processAction.ts`.

***

### Building a custom portal & uploading

Custom minigames are **static HTML** experiences opened in the browser (often in an iframe). The official templates use **React** and **Phaser**, but you may use any stack.

* Ship a build with **`index.html`** at the entry URL.
* In the economy editor, use the **upload** flow to publish the files needed to run your game.

#### Reading and writing player data

* **Read** economy config + farm snapshot + per-player economy state: **Minigames API** `GET /data?type=session` (see below). Use the **portal JWT** in `Authorization`.
* **Write** state transitions that you defined as rules: **Minigames API** `POST /action` with `type: "minigame.action"` (see below).

#### Reference implementation (open source)

* **Template repo (React + portal bootstrap):** [sunflower-land-template](https://github.com/sunflower-land/sunflower-land-template)
  * JWT from query string: [`src/lib/portal/url.ts`](https://github.com/sunflower-land/sunflower-land-template/blob/main/src/lib/portal/url.ts) (`getJwt`, optional `minigamesApiUrl` / `apiUrl` query params).
  * Session fetch: [`src/lib/portal/api.ts`](https://github.com/sunflower-land/sunflower-land-template/blob/main/src/lib/portal/api.ts) (`getPlayerEconomySession`).
  * Action POST: same file (`postPlayerEconomyAction`).
  * Economy dashboard example: `src/examples/ui-resources/` (shop + generator UI driven by session payload).

***

### Authentication (portal JWT)

Before you can load session data or submit actions, you need a **portal JWT**. It identifies the **player** and **farm** and authorises Minigames API calls. The **portal id** inside the JWT must match your economy’s **slug** as saved in the editor.

#### In production (player opens your game)

The game client typically opens your hosted URL with the token in the query string, for example:

`https://your-economy.minigames.sunflower-land.com/?jwt=…`

Read `jwt` in your app and send it on every Minigames request:

```http
Authorization: Bearer <portal-jwt>
```

#### Local development

In the **economy editor**, use **Generate token** (or equivalent) and append the token to your dev server URL, e.g.:

`http://localhost:3000/?jwt=…`

#### Server validation

Handlers verify the JWT with the portal secret; see `src/api/minigames/data.ts` and `src/api/minigames/action.ts`.

***

### Economy data (session payload)

The session response is the structured bundle your minigame uses to render UI and validate client-side optimistically:

* **`farm`** — high-level farm info (e.g. SFL balance string, bumpkin) for context.
* **`playerEconomy`** — balances, active generator jobs (`generating`), activity counters, daily mint buckets, optional `dailyActionUses` / `purchaseCounts`.
* **`actions`** — map of **action id → rule definition** (shop / generator / custom shapes).
* **`items`** — optional map of **token key →** display metadata (name, description, `image`, marketplace `id` / `tradeable`, etc.).
* **`descriptions`**, **`visualTheme`**, **`mainCurrencyToken`** — optional chrome for dashboards.
* **`playUrl`** — canonical hosted game base for this portal (derived server-side; see `playerEconomyOperations.ts`).

Authoritative TypeScript types: `PlayerEconomySessionPayload`.

#### Example `GET /data?type=session` success body

Shape returned by the API (your real `actions` / `items` / balances will differ):

```json
{
  "data": {
    "farm": {
      "balance": "123.45",
      "bumpkin": { }
    },
    "playerEconomy": {
      "balances": { "WOOD": 10, "AXE": 0 },
      "generating": {},
      "activity": 42,
      "dailyActivity": { "date": "2026-04-08", "count": 3 },
      "dailyMinted": { "utcDay": "2026-04-08", "minted": {} },
      "dailyActionUses": { "utcDay": "2026-04-08", "byAction": {} },
      "purchaseCounts": {}
    },
    "actions": {
      "craft_axe": {
        "burn": { "WOOD": { "amount": 3 } },
        "mint": { "AXE": { "amount": 1 } }
      }
    },
    "items": {
      "WOOD": { "name": "Wood", "description": "…", "image": "resources/wood.png" },
      "AXE": { "name": "Axe", "description": "…" }
    },
    "descriptions": {
      "title": "My economy",
      "subtitle": "Craft and gather"
    },
    "visualTheme": "default",
    "playUrl": "https://my-game.minigames.sunflower-land.com"
  }
}
```

#### Session errors (selected)

| HTTP                                            | Meaning                                                                          |
| ----------------------------------------------- | -------------------------------------------------------------------------------- |
| **401**                                         | Missing / invalid JWT.                                                           |
| **422** + `errorCode: "UNKNOWN_PLAYER_ECONOMY"` | No saved economy for this **portal id**, or slug mismatch vs editor.             |
| **422** + `errorCode: "FARM_NOT_FOUND"`         | JWT `farmId` does not match a farm in this API environment.                      |
| **403**                                         | Token minigames / player economies not available for this farm (feature gating). |

***

### Loading session data

**Request**

```http
GET /data?type=session
Authorization: Bearer <portal-jwt>
```

* **Implementation:** `src/api/minigames/data.handler` → `minigameSessionData` → `loadPlayerEconomySession`.
* **Portal id** is taken **only from the JWT** (not from query params).

**Base URL**

The Minigames API is deployed as its own API Gateway; the exact hostname depends on environment (there is not a single guaranteed public host like `minigame-api.sunflower-land.com` in code—always use the base URL from your deployment or client config, e.g. `VITE_MINIGAMES_API_URL` in the [template](https://github.com/sunflower-land/sunflower-land-template)). Hosted **game sites** often follow `{slug}.minigames.sunflower-land.com` (see `MINIGAMES_SITE_HOST_SUFFIX` in `infra/api.ts`); the **API** origin for `fetch` may differ—point your client at the Minigames API base you were given.

**Example client**

See [sunflower-land-template `getPlayerEconomySession`](https://github.com/sunflower-land/sunflower-land-template/blob/main/src/lib/portal/api.ts).

***

### Actions (mutations)

Use actions when the player **collects** something, **starts** a run, **submits** a score, **unlocks** progression, etc. The server **only executes actions that exist** in your published economy config, which keeps outcomes bounded to what you defined.

#### Request

```http
POST /action
Content-Type: application/json
Authorization: Bearer <portal-jwt>
```

**Body (JSON)**

```json
{
  "type": "minigame.action",
  "action": "your_action_id",
  "itemId": "optional-generator-job-id",
  "amounts": {
    "TOKEN_KEY": 5
  }
}
```

| Field     | Required | Description                                                                                                                           |
| --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `type`    | Yes      | Must be `"minigame.action"`.                                                                                                          |
| `action`  | Yes      | **Action id** from the editor (set a readable custom id in the label section when possible).                                          |
| `itemId`  | No       | For **generator collect** (and similar), the **job id** returned when the job was started (or present in `playerEconomy.generating`). |
| `amounts` | No       | Integer map used for **ranged mint** / **ranged burn** rules: token key → chosen amount within your configured min/max.               |

**Success response**

```json
{
  "data": {
    "playerEconomy": {
      "balances": {},
      "generating": {},
      "activity": 0,
      "dailyActivity": { "date": "", "count": 0 },
      "dailyMinted": { "utcDay": "", "minted": {} },
      "dailyActionUses": { "utcDay": "", "byAction": {} },
      "purchaseCounts": {}
    },
    "generatorJobId": "optional-new-job-uuid",
    "collectGrants": [{ "token": "CROP", "amount": 2 }]
  }
}
```

`collectGrants` appears when the server resolves weighted collect outcomes for a collect action.

**Errors**

| HTTP    | Typical cause                                                                   |
| ------- | ------------------------------------------------------------------------------- |
| **400** | Invalid body, unknown action, rule validation failed (`error` message in body). |
| **401** | Invalid JWT.                                                                    |
| **422** | Unknown economy / farm (same ideas as session).                                 |
| **403** | Feature disabled for farm.                                                      |

**Implementation:** `src/api/minigames/action.handler`, `minigameAction.ts`, `executePlayerEconomyAction`.

**Example client**

[sunflower-land-template `postPlayerEconomyAction`](https://github.com/sunflower-land/sunflower-land-template/blob/main/src/lib/portal/api.ts).

***

### Other useful APIs & data

#### Bumpkin / NPC animations (Minigames API)

**Route (same API Gateway as `/data` and `/action`):**

```http
GET /animate/{tokenUri}/{animation}
```

Renders bumpkin (and related) sprite animations for NPCs and large character art. Handler: `src/api/animation/`. Dependencies include layer definitions under `src/domain/bumpkins/` (e.g. `layerImages`, `npcBuilder`, `bumpkinBuilder`). Use this when you need walking, fishing, mining, chopping, jumping, etc., driven by bumpkin `tokenUri` and animation name.

There is also **`GET /bumpkins/metadata/{tokenUri}`** on the same API for metadata helpers.

#### Marketplace: discovering economies & trade context (main Sunflower Land API)

Browsing **Marketplace → Economies** in the game uses the **main** game API (player session JWT, not the portal JWT), for example:

```http
GET {API_URL}/data?type=marketplaceEconomies
Authorization: Bearer <game-user-jwt>
```

Returns economy rows and related leaderboard-style data; see `loadEconomiesMarketplacePageData` and the client helper [`loadEconomiesMarketplaceData.ts`](https://github.com/sunflower-land/sunflower-land/blob/main/src/features/marketplace/actions/loadEconomiesMarketplaceData.ts) in the `sunflower-land` repo.

For a **specific** tradable (listings, offers, sale history) the game uses collection endpoints such as:

```http
GET {API_URL}/collection/economies/{id}?type=economies&economy={economySlug}
Authorization: Bearer <game-user-jwt>
```

See [`loadTradeable.ts`](https://github.com/sunflower-land/sunflower-land/blob/main/src/features/marketplace/actions/loadTradeable.ts). Use this pattern when building dashboards or tools that need **market history** for an economy token—not for mutating minigame state (that remains **`POST /action`** on the Minigames API).

***

### Quick reference

| Goal                                     | API                                        | Auth                              |
| ---------------------------------------- | ------------------------------------------ | --------------------------------- |
| Load config + economy state for minigame | `GET /data?type=session`                   | Portal JWT                        |
| Apply a defined rule                     | `POST /action` (`type: "minigame.action"`) | Portal JWT                        |
| NPC / bumpkin animation asset            | `GET /animate/{tokenUri}/{animation}`      | Per deployment (often public GET) |
| List economies in marketplace            | `GET /data?type=marketplaceEconomies`      | Game user JWT                     |
| Tradeable detail / history               | `GET /collection/...`                      | Game user JWT                     |

For infrastructure wiring (routes, CORS, memory), see **`MinigamesApi`** in `infra/api.ts`.
