From 5035a40623b2667ae7a385a82cb17c6d50d587ce Mon Sep 17 00:00:00 2001
From: Etay Matzliah
Date: Mon, 22 Jun 2026 16:49:41 +0300
Subject: [PATCH 1/8] Update Enjin Farmer sample game docs for Platform v3
The sample game client and server were updated to Enjin Platform v3
(enjin/platform-sample-game-server#6, enjin/platform-sample-game-client-unity#4).
The server was rewritten in .NET on the Enjin Platform C# SDK, so the
docs' raw-GraphQL snippets, env-var config, source links, and setup
steps were all stale.
Implementation breakdown:
- Reframe the server as a .NET 9 / C# SDK app; replace GraphQL snippets
with the SDK's CreateTransaction / TransactionInput calls.
- Document the v3 single-CreateTransaction model, GetTransaction polling
(no event subscriptions yet), managed-wallet provisioning + ENJ drip,
and local SS58 address encoding.
- Swap env-var table for appsettings; add the /api/setup/collection-id
endpoint; drop the removed /login and /wallet routes.
- Point all server source links at the new .cs files and the Unity
links at the merged v3 commit.
Setup guide:
- Prerequisites: .NET 9 SDK instead of Node.js.
- Server config via appsettings.Local.json + dotnet run.
- Replace manual Collection ID paste with the new
"Enjin > Stamp Collection ID" Editor menu.
- Reflect auto-drip in the funding note; drop the obsolete App Key reference.
Co-Authored-By: Claude Opus 4.8 (1M context)
---
.../01-setup-guide.md | 57 +++--
.../03-implementation-breakdown.md | 228 ++++++++++--------
2 files changed, 149 insertions(+), 136 deletions(-)
diff --git a/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md b/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md
index f2c88a2..07e0e24 100644
--- a/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md
+++ b/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md
@@ -16,7 +16,7 @@ In this simple farming game, you'll plant seeds, harvest crops, and collect reso
The project consists of four main components that work together:
- **[Game Client (Unity)](https://github.com/enjin/platform-sample-game-client-unity):** The game itself, where you play and interact with items.
- - **[Game Server (Node.js)](https://github.com/enjin/platform-sample-game-server):** A backend API that the game client communicates with to handle all NFT-related actions like minting and transferring.
+ - **[Game Server (.NET)](https://github.com/enjin/platform-sample-game-server):** A backend API, built on the [Enjin Platform C# SDK](https://github.com/enjin/platform-csharp-sdk), that the game client communicates with to handle all NFT-related actions like minting and transferring.
- **:** The cloud-based service that provides the core NFT infrastructure.
- **Wallet Daemon:** A secure application that manages a wallet on behalf of the game to automatically sign and approve transactions.
@@ -27,7 +27,7 @@ The project consists of four main components that work together:
Before you begin, make sure you have the following installed:
- ✅ **Unity Hub** with **Unity Editor version `6000.0.24f1`**.
- - ✅ **Node.js** (which includes `npm`).
+ - ✅ The **[.NET 9 SDK](https://dotnet.microsoft.com/download)** for running the game server.
- ✅ **Git** for cloning the repositories.
- ✅ An **Enjin Platform account**. If you don't have one, you can create it [here](https://platform.beta.enjin.io/).
- ✅ Some cENJ tokens (can be acquired from the [built-in Canary faucet](/01-getting-started/04-using-the-enjin-platform.md#canary-faucet) in the Platform UI)
@@ -79,21 +79,19 @@ Next, you'll set up your Enjin Platform account and the Wallet Daemon.
Now, let's set up the backend server that powers the game's NFT features.
1. Navigate into the game server directory you cloned: `cd platform-sample-game-server`.
-2. Duplicate the `.env.example` file and rename the copy to `.env`.
-3. Open the `.env` file and fill in the following variables:
- - `PORT=3000` (You can change this if port 3000 is already in use).
- - `JWT_SECRET`: Generate another secure, random string. This is used for authenticating players.
- - `ENJIN_API_URL`: Keep the default `https://platform.canary.enjin.io/graphql` for testing on the Canary network.
- - `ENJIN_API_KEY`: Paste the **API Key Token** from your Enjin Platform account.
- - `DAEMON_WALLET_ADDRESS`: Paste the wallet address you copied from the Wallet Daemon UI.
- - `ENJIN_COLLECTION_ID`: Leave this blank for now.
-4. Install the server dependencies by running `npm install`.
-5. Launch the server for the first time by running `npm run dev`.
-
-The server will now connect to the Enjin Platform, create a new NFT collection for your game, and create the NFT tokens for the in-game resources.
+2. Copy the `appsettings.Sample.json` file and rename the copy to `appsettings.Local.json` (this file is gitignored, so your secrets stay out of version control).
+3. Open `appsettings.Local.json` and fill in the following values:
+ - `Jwt.Secret`: A long, random string (32+ characters). This is used for authenticating players.
+ - `Enjin.ApiToken`: Paste the **API Token** from your Enjin Platform account.
+ - `Enjin.DaemonWalletAddress`: Paste the wallet address you copied from the Wallet Daemon UI.
+
+ The remaining settings have sensible defaults in `appsettings.json` — `Enjin.ApiUrl` already points to the Canary network (`https://platform.canary.enjin.io/graphql`) and `Server.Port` defaults to `3000`. You can leave them as-is for testing.
+4. Launch the server for the first time by running `dotnet run`.
+
+The server will now connect to the Enjin Platform, create a new NFT collection for your game (or reuse an existing one), and create the NFT tokens for the in-game resources. This can take a minute or two on first run while it waits for the on-chain transactions to finalize.
:::info **Important**
-Watch the terminal logs. Once the setup is complete, the server will log the new **Collection ID**. It will look something like this: `Collection and resource tokens are ready. Using collection ID: XXXXXX`. **Copy this Collection ID** and save it. You'll need it to configure the game client.
+The server stores the **Collection ID** it bootstraps in a `state.json` file and reuses it on every restart, so you no longer need to copy it by hand — you'll stamp it onto the game client automatically in [Step 4](#1-stamp-the-collection-id-onto-the-nft-items). When you see `Server listening on http://0.0.0.0:3000` in the logs, the server is ready.
:::
Keep the server and the Wallet Daemon running in the background.
@@ -109,18 +107,17 @@ It's time to set up the Unity project and connect it to your game server.
3. Open the project.
4. Once the project is open in the Unity Editor, you need to configure two things.
-#### 1. Configure the NFT Items
+#### 1. Stamp the Collection ID onto the NFT Items
+
+The game's three `Enjin Item` assets (`GemGreen`, `GoldCoin`, and `GoldCoinBlue`, found in `Assets/Enjin Integration/Scripts/Data/Items`) each need to know the on-chain **Collection ID** the server created. Rather than paste it by hand, the project ships an Editor menu that fetches it from your running server and stamps it onto all three assets for you.
- - In the `Project` window, navigate to `Assets/Enjin Integration/Scripts/Data/Items`.
- - You will see three `Enjin Item` assets: `GemGreen`, `GoldCoin`, and `GoldCoinBlue`.
-
-
-
- - Click on **each one** of these items.
- - In the `Inspector` window for each item, find the **Collection Id** field and paste the `Collection ID` you saved from the game server's terminal log.
-
-
-
+ - Make sure your game server (from Step 3) is still running.
+ - In the Unity Editor menu bar, select **Enjin → Stamp Collection ID onto EnjinItem Assets**.
+ - Confirm the prompt. The Editor calls the server's `/api/setup/collection-id` endpoint and writes the returned ID onto every `EnjinItem` asset.
+
+:::note
+Run this once after the server's first launch. You only need to run it again if the canary state ever resets and the server creates a new collection.
+:::
#### 2. Configure the connection to the Game Server
@@ -150,7 +147,7 @@ You're all set up and ready to play.
:::warning
- If you see an error, double-check that your server is running and that the `Host` and `App Key` in the `EnjinManager` are correct.
+ If you see an error, double-check that your server is running and that the `Host` in the `EnjinManager` is correct.
:::
5. In the game, click the **Menu** button (top-right), then **Login**.
@@ -167,9 +164,9 @@ You're all set up and ready to play.
-:::warning cENJ funds are required
-New managed wallet have no funds. To melt or transfer tokens out of a managed wallet, you'll need to fund it with some cENJ, or set up a fuel tank.
-To receive cENJ funds for testing, use the [built-in Canary faucet](/01-getting-started/04-using-the-enjin-platform.md#canary-faucet) in the Platform UI.
+:::warning Keep your daemon wallet funded
+New managed wallets start empty, so the server automatically drips a little cENJ (1 ENJ by default) from your **daemon wallet** to each new player wallet so it can pay the fees for melting and transferring. This means the daemon wallet itself needs cENJ — to create the collection, mint tokens, and fund new players.
+To top up the daemon wallet for testing, use the [built-in Canary faucet](/01-getting-started/04-using-the-enjin-platform.md#canary-faucet) in the Platform UI.
:::
:::info Understanding the code
diff --git a/docs/02-guides/01-platform/05-enjin-farmer-sample-game/03-implementation-breakdown.md b/docs/02-guides/01-platform/05-enjin-farmer-sample-game/03-implementation-breakdown.md
index 21bd94d..24288f2 100644
--- a/docs/02-guides/01-platform/05-enjin-farmer-sample-game/03-implementation-breakdown.md
+++ b/docs/02-guides/01-platform/05-enjin-farmer-sample-game/03-implementation-breakdown.md
@@ -2,7 +2,7 @@
title: "Enjin Farmer: Implementation Breakdown"
sidebar_label: "Implementation Breakdown"
slug: "implementation-breakdown"
-description: "Dive deep into the code and architecture of the Enjin Farmer sample game. This technical breakdown explains the implementation flow, from the Unity client to the Node.js game server. Understand the key GraphQL mutations and API calls used to mint, transfer, and manage NFTs in your game."
+description: "Dive deep into the code and architecture of the Enjin Farmer sample game. This technical breakdown explains the implementation flow, from the Unity client to the .NET game server. Understand the key Enjin Platform C# SDK calls used to mint, transfer, and manage NFTs in your game."
---
import GlossaryTerm from '@site/src/components/GlossaryTerm';
@@ -12,131 +12,146 @@ The **Enjin Farmer** sample project demonstrates a basic Enjin Platform integrat
The project consists of two main components:
* **🎮 Unity Game (Client):** The front-end game that players interact with. It handles gameplay, visuals, and user input, communicating with the game server to perform blockchain actions.
- * **🖥️ Game Server (Backend):** A Node.js application that manages all Enjin Platform logic. It securely handles wallet creation, token minting, and other on-chain operations on behalf of the players.
+ * **🖥️ Game Server (Backend):** A .NET application that manages all Enjin Platform logic using the [Enjin Platform C# SDK](https://github.com/enjin/platform-csharp-sdk). It securely handles wallet creation, token minting, and other on-chain operations on behalf of the players.
### 💡 Important Considerations
Before you begin, please keep the following in mind:
* **Demonstration Purpose:** This is a simplified example designed to showcase a basic integration. It is **not suitable for a production environment** as is.
- * **WebSockets:** This implementation does not use [WebSocket events](/03-api-reference/03-websocket-events.md). In a real-world application, WebSockets can simplify the process of listening for transaction finalization and receiving real-time updates, such as when a user receives an NFT from an external source like the marketplace.
- * **Wallet Funding:** New managed wallets are created without any funds. To cover network fees for actions like melting or transferring tokens, you must either fund each wallet individually or use a [Fuel Tank](/02-guides/01-platform/02-managing-users/04-using-fuel-tanks.md) to subsidize transactions for all your users.
+ * **Polling, not subscriptions:** The Enjin Platform API doesn't yet expose [WebSocket events](/03-api-reference/03-websocket-events.md), so after submitting a transaction the server polls the `GetTransaction` query until it finalizes. Real-time event streaming is planned; once available it can simplify listening for finalization and for tokens arriving from external sources like the marketplace.
+ * **Wallet Funding:** New managed wallets start empty. So they can pay the network fees for melting and transferring, this sample has the server automatically drip a small amount of cENJ (1 ENJ by default) from the daemon wallet to each new managed wallet. In a real-world application you'd typically use a [Fuel Tank](/02-guides/01-platform/02-managing-users/04-using-fuel-tanks.md) to subsidize transactions for all your users instead.
* **On-chain actions aren't instant:** This sample melts and mints on-chain whenever items change hands, which takes seconds to finalize — fine for a farming demo, but unplayable for real-time action. For a production pattern that keeps item use instant while preserving on-chain ownership, see [Hot & Cold Inventories](/02-guides/01-platform/03-advanced-mechanics/07-hot-cold-inventories.md).
-----
## 🖥️ Game Server
-The game server is a RESTful API built with Node.js and Express. It serves as the secure bridge between the game client and the Enjin Platform. The main entry point is the [`src/index.js` file](https://github.com/enjin/platform-sample-game-server/blob/4b84cf06df32bf2230197ffad14b2c7e0884e1f4/src/index.js).
+The game server is a .NET 9 minimal-API application that talks to the Enjin Platform through the [Enjin Platform C# SDK](https://github.com/enjin/platform-csharp-sdk). It serves as the secure bridge between the game client and the platform — it holds the Enjin Platform API token, which the Unity client never sees. The main entry point is the [`Program.cs` file](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Program.cs), where the host, dependency injection, JWT authentication, and on-chain bootstrap are wired up.
-### Environment Variables
+### Configuration
-The server is configured using the following environment variables:
+The server is configured through `appsettings.json` (defaults) plus an `appsettings.Local.json` (gitignored) for your secrets. Only three values must be set; the rest have sensible defaults.
-| Variable | Description |
+| Setting | Description |
| :--- | :--- |
-| `PORT` | The port the server listens on. Defaults to `3000`. |
-| `JWT_SECRET` | A secure, random string used for signing player authentication tokens. |
-| `ENJIN_API_URL` | The Enjin Platform API URL. Use `https://platform.canary.enjin.io/graphql` for testing (Canary Network) or `https://platform.beta.enjin.io/graphql` for production. |
-| `ENJIN_API_KEY` | Your API Key Token obtained from the Enjin Platform. |
-| `DAEMON_WALLET_ADDRESS` | The address of your Wallet Daemon. This wallet receives the initial supply of all created tokens. |
-| `ENJIN_COLLECTION_ID` | The ID of the Enjin Farmer collection. If left blank, the server will create a new collection on startup. |
+| `Jwt.Secret` | A long, random string used for signing player authentication tokens. |
+| `Enjin.ApiToken` | Your API token obtained from the Enjin Platform. |
+| `Enjin.DaemonWalletAddress` | The SS58 address of your . This wallet owns the collection, mints the resource tokens, and funds new player wallets. |
+| `Enjin.ApiUrl` | The Enjin Platform API URL. Defaults to `https://platform.canary.enjin.io/graphql` (Canary). Switch to the production endpoint when you ship. |
+| `Enjin.Network` / `Enjin.Chain` | The target network and chain. Defaults to `Canary` / `Matrix`. |
+| `Server.Port` | The port the server listens on. Defaults to `3000` (the Unity client expects `3000`). |
+| `Enjin.CollectionName` | Used to find or reuse an existing collection so a new one isn't created on every run. |
+| `Enjin.ResourceTokens` | The resource tokens to create (Gold Coin, Gold Coin (Blue), Green Gem). The Unity client ships matching `EnjinItem` assets for token IDs `1`, `2`, and `3`. |
+| `Enjin.Ss58Prefix` | The prefix used to encode wallet public keys into addresses. `9030` = Canary Matrixchain, `1110` = Enjin Mainnet Matrixchain. |
+| `Enjin.DripEnjEnabled` / `Enjin.DripEnjAmount` | Whether to auto-fund each new managed wallet, and how much (default `1` ENJ). |
+
+The strongly-typed config classes live in [`Services/Options.cs`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/Options.cs), and all the defaults are in [`appsettings.json`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/appsettings.json).
### Server Initialization & Collection Setup
-On startup, the server performs a one-time setup to ensure the necessary blockchain assets exist.
-
-1. **Check for Collection:** The server first checks if an `ENJIN_COLLECTION_ID` has been provided.
-2. **Create Collection:** If the ID is missing, the server calls the [`createCollection` function](https://github.com/enjin/platform-sample-game-server/blob/4b84cf06df32bf2230197ffad14b2c7e0884e1f4/src/services/enjinService.js#L69), which executes the `CreateCollection` mutation on the Enjin Platform. The automatically signs the request.
- ```graphql
- mutation CreateCollection($name: String!, ...) {
- CreateCollection(...) {
- id
- method
- state
- }
- }
+On startup — before serving any requests — the server runs [`PrepareCollectionAsync`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L80) to make sure the collection and resource tokens exist.
+
+1. **Reuse or create the collection:** If no collection ID is stored yet, the server first [queries `GetCollections`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L142) for one owned by the daemon wallet whose `name` attribute matches `Enjin.CollectionName` — this avoids creating duplicates if local state was lost. If none exists, it [creates one](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L170).
+
+ In v3, every on-chain mutation goes through a single **`CreateTransaction`** mutation: you populate a `TransactionInput` with one of its method fields (`CreateCollection`, `CreateToken`, `MintToken`, `BurnToken`, `TransferToken`, …) and submit it. Collection creation looks like this:
+ ```csharp
+ var transaction = new TransactionInput
+ {
+ CreateCollection = new CreateCollectionInput
+ {
+ Attributes = new List
+ {
+ new() { Key = "name", Value = _opts.CollectionName },
+ // banner_image, media, ...
+ },
+ },
+ };
+
+ var submitted = await CreateTransactionAsync(transaction, signerExternalId: null, ct);
+ await WaitForFinalizationAsync(submitted.Uuid!, "collection creation", ct);
```
-3. **Monitor Transaction:** The mutation returns a request ID. The server then [polls the `GetTransaction` query](https://github.com/enjin/platform-sample-game-server/blob/4b84cf06df32bf2230197ffad14b2c7e0884e1f4/src/services/enjinService.js#L15) until the transaction `state` is `FINALIZED` and the `result` is `EXTRINSIC_SUCCESS`.
- ```graphql
- query GetTransaction($requestId: Int!) {
- GetTransaction(id: $requestId) {
- state
- result
- events { ... }
- }
- }
- ```
-4. **Extract Collection ID:** Once finalized, the server [extracts the new collection ID from the transaction's events](https://github.com/enjin/platform-sample-game-server/blob/4b84cf06df32bf2230197ffad14b2c7e0884e1f4/src/services/enjinService.js#L43) and assigns it to the `ENJIN_COLLECTION_ID` variable.
-5. **Create NFTs:** Using a similar process, the server then [creates the three resource NFTs](https://github.com/enjin/platform-sample-game-server/blob/4b84cf06df32bf2230197ffad14b2c7e0884e1f4/src/services/enjinService.js#L116) ([Gold Coin](https://github.com/enjin/platform-sample-game-server/blob/4b84cf06df32bf2230197ffad14b2c7e0884e1f4/src/services/enjinService.js#L6), [Gold Coin (Blue)](https://github.com/enjin/platform-sample-game-server/blob/4b84cf06df32bf2230197ffad14b2c7e0884e1f4/src/services/enjinService.js#L7), and [Green Gem](https://github.com/enjin/platform-sample-game-server/blob/4b84cf06df32bf2230197ffad14b2c7e0884e1f4/src/services/enjinService.js#L8)) within the collection by calling the `CreateToken` mutation for each and waiting for finalization.
- ```graphql
- mutation CreateToken($collectionId: BigInt!, $name: String!, ...) {
- CreateToken(collectionId: $collectionId, params: { ... }) {
- id
- method
- state
- }
- }
+ [`CreateTransactionAsync`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L587) builds the mutation with the SDK's query builders and sends it. The picks the transaction up and signs it automatically:
+ ```csharp
+ var mutation = new MutationQueryBuilder().WithCreateTransaction(
+ new TransactionQueryBuilder().WithUuid().WithState(),
+ _network, _chain,
+ transaction: input,
+ signerExternalId: signerExternalId);
+
+ var resp = await _client.SendMutation(mutation);
```
-After this setup is complete, the server starts listening for API requests.
+2. **Wait for finalization:** `CreateTransaction` returns a transaction UUID. The server then [polls `GetTransaction`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L613) by that UUID until `State` is `FINALIZED` (it throws if the transaction ends up `FAILED`, `ABANDONED`, or `TIMEOUT`). Since v3 doesn't surface a transaction's emitted events, the new collection's ID is recovered by re-querying `GetCollections` and matching on the `name` attribute.
+
+3. **Create resource tokens:** For each entry in `Enjin.ResourceTokens`, the server [checks whether the token already exists](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L205) with a `GetToken` query and, if not, [creates it](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L224) with a `CreateToken` input.
+
+4. **Persist:** The resolved collection ID is written to `state.json` so subsequent runs reuse it instead of re-creating anything.
+
+After this, the server starts listening and logs the collection ID and `Server listening on http://0.0.0.0:3000`.
+
+### Managed Wallets & Addresses
+
+Each player gets one [managed wallet](/02-guides/01-platform/02-managing-users/03-using-managed-wallets.md), keyed by their email as the `externalId`. The server [ensures it exists](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L279) by calling the `CreateManagedWallet` mutation; because the platform provisions wallets asynchronously, the server then polls `GetManagedWallet` until the wallet is queryable. The platform returns the wallet's **public key** as hex, which the server [SS58-encodes locally](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L387) (using `Enjin.Ss58Prefix`) into the address used as a mint recipient and holder. Each new wallet is also [dripped a little ENJ](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L318) from the daemon so it can pay transaction fees.
### API Endpoints
-The server exposes several endpoints to handle game actions. The `wallet` and `token` endpoints are protected by a [JWT authentication middleware](https://github.com/enjin/platform-sample-game-server/blob/4b84cf06df32bf2230197ffad14b2c7e0884e1f4/src/middlewares/jwtAuth.js), which verifies the player's identity before processing the request.
+The server exposes several endpoints to handle game actions. The `wallet` and `token` route groups require a valid JWT (`.RequireAuthorization()`, backed by the [JWT bearer configuration in `Program.cs`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Program.cs#L70)). Each protected handler reads the player's `email` claim and uses it as the `externalId` when talking to the platform.
#### Authentication
- * [`GET /api/auth/health-check`](https://github.com/enjin/platform-sample-game-server/blob/4b84cf06df32bf2230197ffad14b2c7e0884e1f4/src/routes/auth.js#L7): A simple endpoint to verify that the server is online.
- * [`POST /api/auth/register`](https://github.com/enjin/platform-sample-game-server/blob/4b84cf06df32bf2230197ffad14b2c7e0884e1f4/src/routes/auth.js#L14): Creates a new player and an associated [managed wallet](/02-guides/01-platform/02-managing-users/03-using-managed-wallets.md). To create the wallet, it calls the [`CreateWallet` mutation](https://github.com/enjin/platform-sample-game-server/blob/4b84cf06df32bf2230197ffad14b2c7e0884e1f4/src/services/enjinService.js#L271), using the player's [email address as the unique `externalId`](https://github.com/enjin/platform-sample-game-server/blob/4b84cf06df32bf2230197ffad14b2c7e0884e1f4/src/routes/auth.js#L18).
- ```graphql
- mutation CreateWallet($externalId: String!) {
- CreateWallet(externalId: $externalId)
- }
- ```
- * [`POST /api/auth/login`](https://github.com/enjin/platform-sample-game-server/blob/4b84cf06df32bf2230197ffad14b2c7e0884e1f4/src/routes/auth.js#L36): Logs in an existing player, returning their wallet address and a JWT.
+ * [`GET /api/auth/health-check`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Endpoints/AuthEndpoints.cs#L16): A simple endpoint to verify that the server is online.
+ * [`POST /api/auth/register`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Endpoints/AuthEndpoints.cs#L23): Registers a new player **or** logs in an existing one (the client uses this single endpoint for both), returning a JWT. On first registration it also [provisions the player's managed wallet](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Endpoints/AuthEndpoints.cs#L45) and drips it some cENJ, using the player's [email address as the unique `externalId`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/AuthService.cs#L29).
#### Wallet Management
- * [`POST /api/wallet/create`](https://github.com/enjin/platform-sample-game-server/blob/4b84cf06df32bf2230197ffad14b2c7e0884e1f4/src/routes/wallet.js#L28): Creates a new managed wallet
- * [`POST /api/wallet/get`](https://github.com/enjin/platform-sample-game-server/blob/4b84cf06df32bf2230197ffad14b2c7e0884e1f4/src/routes/wallet.js#L8): Retrieves details for the authenticated player's managed wallet.
- * [`GET /api/wallet/get-tokens`](https://github.com/enjin/platform-sample-game-server/blob/4b84cf06df32bf2230197ffad14b2c7e0884e1f4/src/routes/wallet.js#L48): Retrieves the player's managed wallet and all tokens it holds. It calls the [`GetWallet` query](https://github.com/enjin/platform-sample-game-server/blob/4b84cf06df32bf2230197ffad14b2c7e0884e1f4/src/services/enjinService.js#L208) (using [GraphQL Pagination](/01-getting-started/05-using-enjin-api/01-how-to-use-graphql.md#pagination) to loop through all pages of results, ensuring the complete inventory is fetched).
- ```graphql
- query GetWalletTokens($externalId: String!) {
- GetWallet(externalId: $externalId) {
- account { ... }
- tokenAccounts(...) { ... }
- }
- }
- ```
+ * [`GET /api/wallet/get-tokens`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Endpoints/WalletEndpoints.cs#L16): Retrieves the player's managed wallet and the resource-token balances it holds. It [looks up the wallet](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L400) via `GetManagedWallet`, then queries `GetToken` for each resource token and filters the holder list down to the player's address.
#### Token Actions
- * [`POST /api/token/mint`](https://github.com/enjin/platform-sample-game-server/blob/4b84cf06df32bf2230197ffad14b2c7e0884e1f4/src/routes/token.js#L8): Mints a token to the player's wallet. The server calls the [`MintToken` mutation](https://github.com/enjin/platform-sample-game-server/blob/4b84cf06df32bf2230197ffad14b2c7e0884e1f4/src/services/enjinService.js#L317). The recipient address is extracted from the player's authenticated session.
- ```graphql
- mutation mintToken($recipient: String!, $collectionId: BigInt!, ...) {
- MintToken(recipient: $recipient, collectionId: $collectionId, ...) {
- id
- }
- }
+ * [`POST /api/token/mint`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Endpoints/TokenEndpoints.cs#L14): Mints a token into the player's managed wallet via [`MintTokenAsync`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L512). The daemon signs it.
+ ```csharp
+ var input = new TransactionInput
+ {
+ MintToken = new MintTokenInput
+ {
+ Recipient = recipientAddress,
+ CollectionId = RequireCollectionId(),
+ TokenId = tokenId,
+ Amount = amount,
+ },
+ };
```
- * [`POST /api/token/melt`](https://github.com/enjin/platform-sample-game-server/blob/4b84cf06df32bf2230197ffad14b2c7e0884e1f4/src/routes/token.js#L29): Melts a token from the player's wallet. This uses the [`Burn` mutation](https://github.com/enjin/platform-sample-game-server/blob/4b84cf06df32bf2230197ffad14b2c7e0884e1f4/src/services/enjinService.js#L371). Since the token needs to be melted from the managed wallet's account, it needs to be signed by the managed wallet. For that, the player's managed wallet address is specified as the `signingAccount`. ([Learn more about managed wallets here](/02-guides/01-platform/02-managing-users/03-using-managed-wallets.md))
- ```graphql
- mutation burnToken($signingAccount: String!, $collectionId: BigInt!, ...) {
- Burn(signingAccount: $signingAccount, collectionId: $collectionId, ...) {
- id
- }
- }
+ * [`POST /api/token/melt`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Endpoints/TokenEndpoints.cs#L36): Melts (burns) a token from the player's wallet via [`MeltTokenAsync`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L532). Since the burn must come from the player's managed wallet, the call passes the player's email as `signerExternalId` so the daemon signs on that wallet's behalf. ([Learn more about managed wallets here](/02-guides/01-platform/02-managing-users/03-using-managed-wallets.md))
+ ```csharp
+ var input = new TransactionInput
+ {
+ BurnToken = new BurnTokenInput
+ {
+ CollectionId = RequireCollectionId(),
+ TokenId = tokenId,
+ Amount = amount,
+ },
+ };
```
- * [`POST /api/token/transfer`](https://github.com/enjin/platform-sample-game-server/blob/4b84cf06df32bf2230197ffad14b2c7e0884e1f4/src/routes/token.js#L50): Transfers a token from the player's managed wallet to another address. This uses the [`SimpleTransferToken` mutation](https://github.com/enjin/platform-sample-game-server/blob/4b84cf06df32bf2230197ffad14b2c7e0884e1f4/src/services/enjinService.js#L425), again specifying the player's managed wallet as the `signingAccount`.
- ```graphql
- mutation transferToken($signingAccount: String!, $recipient: String!, ...) {
- SimpleTransferToken(signingAccount: $signingAccount, recipient: $recipient, ...) {
- id
- }
- }
+ * [`POST /api/token/transfer`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Endpoints/TokenEndpoints.cs#L57): Transfers a token from the player's managed wallet to another SS58 address via [`TransferTokenAsync`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L551), again signed via the player's `signerExternalId`.
+ ```csharp
+ var input = new TransactionInput
+ {
+ TransferToken = new TransferTokenInput
+ {
+ Recipient = recipientAddress,
+ CollectionId = RequireCollectionId(),
+ TokenId = tokenId,
+ Amount = amount,
+ },
+ };
```
+#### Setup
+
+ * [`GET /api/setup/collection-id`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Endpoints/SetupEndpoints.cs#L26): Returns the collection ID the server bootstrapped. This is called once by the Unity Editor's "Stamp Collection ID" menu (see [Setup Guide → Step 4](/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md#1-stamp-the-collection-id-onto-the-nft-items)) to write the ID onto the client's NFT item assets; the running game never calls it.
+
-----
## 🎮 Unity Game
@@ -148,17 +163,18 @@ The Unity game is the client-facing part of the project. It focuses on gameplay
The Enjin integration is managed by a few key scripts and a central prefab:
* **`EnjinManager.prefab`**: The heart of the integration. This prefab is added to the `Farm_Outdoor` scene and configures the **Host URL** (e.g., `http://localhost:3000`) in the Inspector to connect to your game server.
- * **[`EnjinManager.cs`](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/Enjin%20Integration/Scripts/Core/EnjinManager.cs)**: A singleton controller that manages the player's session (auth token, wallet data) and exposes high-level methods like `MintToken()` for other game scripts to use.
- * **[`EnjinApiService.cs`](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/Enjin%20Integration/Scripts/API/EnjinApiService.cs)**: Handles all REST API communication with the game server using Unity's `UnityWebRequest`.
- * **[`EnjinItem.cs`](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/Enjin%20Integration/Scripts/Data/EnjinItem.cs)**: A `ScriptableObject` that represents the data of a blockchain item, such as its display name and its corresponding on-chain token ID.
- * **UI Scripts** ([`BackpackUI.cs`](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/Enjin%20Integration/Scripts/UI/BackpackUI.cs), [`BackpackItemController.cs`](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/Enjin%20Integration/Scripts/UI/BackpackItemController.cs)): Scripts that manage the UI for viewing and interacting with the player's NFT inventory.
+ * **[`EnjinManager.cs`](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Scripts/Core/EnjinManager.cs)**: A singleton controller that manages the player's session (auth token, wallet data) and exposes high-level methods like `MintToken()` for other game scripts to use.
+ * **[`EnjinApiService.cs`](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Scripts/API/EnjinApiService.cs)**: Handles all REST API communication with the game server using Unity's `UnityWebRequest`.
+ * **[`EnjinItem.cs`](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Scripts/Data/EnjinItem.cs)**: A `ScriptableObject` that represents the data of a blockchain item, such as its display name and its corresponding on-chain token ID.
+ * **[`StampCollectionIdMenu.cs`](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Editor/StampCollectionIdMenu.cs)**: An Editor utility that adds the **Enjin → Stamp Collection ID onto EnjinItem Assets** menu. It calls the server's `/api/setup/collection-id` endpoint and writes the returned ID onto every `EnjinItem` asset, so you don't have to paste it by hand.
+ * **UI Scripts** ([`BackpackUI.cs`](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Scripts/UI/BackpackUI.cs), [`BackpackItemController.cs`](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Scripts/UI/BackpackItemController.cs)): Scripts that manage the UI for viewing and interacting with the player's NFT inventory.
### Initial Setup & Player Authentication
-1. **Health Check**: On launch, the client [calls the `/api/auth/health-check` endpoint](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/Enjin%20Integration/Scripts/API/EnjinApiService.cs#L36) to ensure the server is available.
-2. **Login/Register**: From the login screen, the player [clicks "Login"](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/HappyHarvest/Common/UI/SettingMenu/Script/SettingMenu.cs#L145), which calls the [`EnjinManager.Instance.RegisterAndLogin()` method](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/Enjin%20Integration/Scripts/Core/EnjinManager.cs#L141).
-3. **API Request**: This triggers `EnjinApiService` to [send a POST request to the `/api/auth/register` endpoint](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/Enjin%20Integration/Scripts/API/EnjinApiService.cs#L69).
-4. **Store Auth Token**: The server responds with a JWT authentication token. The client [saves this token locally using `PlayerPrefs`](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/Enjin%20Integration/Scripts/Core/EnjinManager.cs#L152) and [loads it on subsequent launches](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/Enjin%20Integration/Scripts/Core/EnjinManager.cs#L40) for a seamless experience.
+1. **Health Check**: On launch, the client [calls the `/api/auth/health-check` endpoint](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Scripts/API/EnjinApiService.cs#L43) to ensure the server is available.
+2. **Login/Register**: From the login screen, the player [clicks "Login"](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/HappyHarvest/Common/UI/SettingMenu/Script/SettingMenu.cs#L129), which calls the [`EnjinManager.Instance.RegisterAndLogin()` method](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Scripts/Core/EnjinManager.cs#L174).
+3. **API Request**: This triggers `EnjinApiService` to [send a POST request to the `/api/auth/register` endpoint](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Scripts/API/EnjinApiService.cs#L72).
+4. **Store Auth Token**: The server responds with a JWT authentication token. The client [saves this token locally using `PlayerPrefs`](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Scripts/Core/EnjinManager.cs#L208) and [loads it on subsequent launches](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Scripts/Core/EnjinManager.cs#L219) for a seamless experience.
### In-Game NFT Interactions
@@ -166,21 +182,21 @@ All blockchain actions are initiated by the client but securely executed by the
#### Harvesting and Minting Tokens
-When a player [harvests a crop with the Hoe tool](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/HappyHarvest/Scripts/Items/Hoe.cs#L18), they have a chance to find a resource token.
+When a player [harvests a crop with the Hoe tool](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/HappyHarvest/Scripts/Items/Hoe.cs#L18), they have a chance to find a resource token.
-1. An `EnjinToken` GameObject [appears on the harvested tile](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/Enjin%20Integration/Scripts/Core/EnjinManager.cs#L218).
-2. When the player collects this GameObject, its [`InteractedWith()` method](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/Enjin%20Integration/Scripts/Gameplay/EnjinToken.cs#L20) is triggered.
-3. This calls [`EnjinItem.Collect()`](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/Enjin%20Integration/Scripts/Data/EnjinItem.cs#L37), which in turn calls `EnjinManager.Instance.MintToken()`.
-4. `EnjinManager` then uses `EnjinApiService` to [send a request to the `/api/token/mint` endpoint](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/Enjin%20Integration/Scripts/API/EnjinApiService.cs#L97).
+1. An `EnjinToken` GameObject [appears on the harvested tile](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Scripts/Core/EnjinManager.cs#L262).
+2. When the player collects this GameObject, its [`InteractedWith()` method](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Scripts/Gameplay/EnjinToken.cs#L21) is triggered.
+3. This calls [`EnjinItem.Collect()`](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Scripts/Data/EnjinItem.cs#L37), which in turn calls `EnjinManager.Instance.MintToken()`.
+4. `EnjinManager` then uses `EnjinApiService` to [send a request to the `/api/token/mint` endpoint](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Scripts/API/EnjinApiService.cs#L97).
#### Viewing the Wallet (Backpack UI)
1. Clicking the backpack icon opens the inventory screen, managed by `BackpackUI.cs`.
-2. The UI [calls `EnjinManager.Instance.GetManagedWalletTokens()`](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/Enjin%20Integration/Scripts/UI/BackpackUI.cs#L47), which [sends a request to the `/api/wallet/get-tokens` endpoint](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/Enjin%20Integration/Scripts/API/EnjinApiService.cs#L205).
-3. The server saves the list of tokens, and the `BackpackUI` [populates the view with the data](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/Enjin%20Integration/Scripts/UI/BackpackUI.cs#L81).
-4. The `BackpackUI` also [subscribes to the `EnjinManager.Instance.OnWalletUpdated` event](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/Enjin%20Integration/Scripts/UI/BackpackUI.cs#L39) to automatically refresh the inventory after a token is [minted](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/Enjin%20Integration/Scripts/Core/EnjinManager.cs#L95), [melted](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/Enjin%20Integration/Scripts/Core/EnjinManager.cs#L107), or [transferred](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/Enjin%20Integration/Scripts/Core/EnjinManager.cs#L119).
+2. The UI [calls `EnjinManager.Instance.GetManagedWalletTokens()`](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Scripts/UI/BackpackUI.cs#L117), which [sends a request to the `/api/wallet/get-tokens` endpoint](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Scripts/API/EnjinApiService.cs#L205).
+3. The `BackpackUI` then [populates the view with the returned tokens](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Scripts/UI/BackpackUI.cs#L80).
+4. The `BackpackUI` also [subscribes to the `EnjinManager.Instance.OnWalletUpdated` event](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Scripts/UI/BackpackUI.cs#L46) to automatically refresh the inventory after a token is [minted](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Scripts/Core/EnjinManager.cs#L116), [melted](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Scripts/Core/EnjinManager.cs#L132), or [transferred](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Scripts/Core/EnjinManager.cs#L148).
#### Melting and Transferring Tokens
- * **Melting**: The player [clicks "Melt" in the backpack](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/Enjin%20Integration/Scripts/UI/BackpackItemController.cs#L38). This flows through [`EnjinManager`](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/Enjin%20Integration/Scripts/Core/EnjinManager.cs#L100) and sends a request to the [`/api/token/melt` endpoint](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/Enjin%20Integration/Scripts/API/EnjinApiService.cs#L126).
- * **Transferring**: The player [clicks "Send"](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/Enjin%20Integration/Scripts/UI/BackpackItemController.cs#L59), sending a request to the [`/api/token/transfer` endpoint](https://github.com/enjin/platform-sample-game-client-unity/blob/e154e83723a76861ca762f4b998ef8c8a44ee44f/Assets/Enjin%20Integration/Scripts/API/EnjinApiService.cs#L155).
\ No newline at end of file
+ * **Melting**: The player [clicks "Melt" in the backpack](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Scripts/UI/BackpackItemController.cs#L53). This flows through [`EnjinManager.MeltToken()`](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Scripts/Core/EnjinManager.cs#L125) and sends a request to the [`/api/token/melt` endpoint](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Scripts/API/EnjinApiService.cs#L126).
+ * **Transferring**: The player [clicks "Send"](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Scripts/UI/BackpackItemController.cs#L75), which flows through [`EnjinManager.TransferToken()`](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Scripts/Core/EnjinManager.cs#L141) and sends a request to the [`/api/token/transfer` endpoint](https://github.com/enjin/platform-sample-game-client-unity/blob/9101b08a7f7ea2a4685c315cfb55864a6be43a25/Assets/Enjin%20Integration/Scripts/API/EnjinApiService.cs#L155).
From 3b62a6451d00ca847f3030f4f62f37934702a04b Mon Sep 17 00:00:00 2001
From: Etay Matzliah
Date: Wed, 24 Jun 2026 22:19:32 +0300
Subject: [PATCH 2/8] Farmer docs: drop Enjin.ApiUrl, update Wallet Daemon
setup, add .NET SDK check
- The server repo removed Enjin.ApiUrl (PLA-2449); the C# SDK now defaults
to the platform host. Drop the setting from the config table and the
server-config step, and bump server source links to the new commit
(EnjinService.cs anchors shifted after the constructor change).
- Rewrite the Wallet Daemon setup for the v3 CLI daemon: download from
enj.in/daemon, configure .env (PLATFORM_KEY + KEY_PASS), run the binary,
and copy the printed Canary Matrixchain address. The old downloadable
UI executable is gone.
- Add a .NET 9 SDK prerequisite check to Step 3 (verify with
`dotnet --list-sdks`) to head off the "No .NET SDKs were found" error.
Co-Authored-By: Claude Opus 4.8 (1M context)
---
.../01-setup-guide.md | 26 +++++++++++----
.../03-implementation-breakdown.md | 33 +++++++++----------
2 files changed, 35 insertions(+), 24 deletions(-)
diff --git a/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md b/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md
index 07e0e24..865d148 100644
--- a/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md
+++ b/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md
@@ -50,7 +50,7 @@ First, you need to download the game client, the game server, and the Wallet Dae
git clone https://github.com/enjin/platform-sample-game-server.git
```
-3. **Download the Wallet Daemon:** Download the latest executable for your operating system from the [Wallet Daemon releases page](https://github.com/enjin/wallet-daemon-ui/releases).
+3. **Download the Wallet Daemon:** Download the prebuilt daemon for your operating system from [https://enj.in/daemon](https://enj.in/daemon) and extract it into a dedicated directory.
-----
@@ -67,10 +67,18 @@ Next, you'll set up your Enjin Platform account and the Wallet Daemon.
### Wallet Daemon
-1. Run the Wallet Daemon executable you downloaded.
-2. Follow the on-screen instructions to configure it, and enter the **API Token** you just copied from the Enjin Platform into the Settings → Canary Matrixchain input field. For a detailed guide, see the [Wallet Daemon documentation](/01-getting-started/06-using-wallet-daemon.md).
-3. Select the Enjin Platform Canary in the network dropdown menu, and run the Wallet Daemon.
-4. Once configured and running, the Wallet Daemon UI will display a wallet address. **Copy this wallet address** for the next step.
+The Wallet Daemon is the signer that approves your game server's transactions. It runs from the command line and is configured with a `.env` file.
+
+1. In the daemon directory you extracted, copy the `.env.example` file to `.env`.
+2. Open `.env` and set the two required values:
+ - `PLATFORM_KEY`: The **API Token** you just copied from the Enjin Platform.
+ - `KEY_PASS`: A unique, high-entropy password used to encrypt the wallet seed. Store it somewhere safe — you'll need it every time the daemon starts.
+3. Start the daemon — `./wallet-daemon` (or `.\wallet-daemon.exe` on Windows). On first run it generates a new wallet, writes the encrypted seed to `wallet.seed`, and prints an SS58 address for each network.
+4. From the printed addresses, **copy the Canary Matrixchain address** — that's the network this sample uses. You'll need it in the next step.
+
+:::tip
+For a detailed guide — including Docker, AWS, importing an existing seed, and backup guidance — see the [Wallet Daemon documentation](/01-getting-started/06-using-wallet-daemon.md).
+:::
-----
@@ -78,14 +86,18 @@ Next, you'll set up your Enjin Platform account and the Wallet Daemon.
Now, let's set up the backend server that powers the game's NFT features.
+:::warning You need the .NET 9 SDK
+The server runs on .NET, so the **.NET 9 SDK** (not just the runtime) must be installed. Confirm it by running `dotnet --list-sdks` — you should see a `9.x.x` entry. If the command isn't found or lists nothing, install the [.NET 9 SDK](https://dotnet.microsoft.com/download), then open a **new** terminal so your `PATH` picks it up.
+:::
+
1. Navigate into the game server directory you cloned: `cd platform-sample-game-server`.
2. Copy the `appsettings.Sample.json` file and rename the copy to `appsettings.Local.json` (this file is gitignored, so your secrets stay out of version control).
3. Open `appsettings.Local.json` and fill in the following values:
- `Jwt.Secret`: A long, random string (32+ characters). This is used for authenticating players.
- `Enjin.ApiToken`: Paste the **API Token** from your Enjin Platform account.
- - `Enjin.DaemonWalletAddress`: Paste the wallet address you copied from the Wallet Daemon UI.
+ - `Enjin.DaemonWalletAddress`: Paste the daemon's **Canary Matrixchain** address you copied in the previous step.
- The remaining settings have sensible defaults in `appsettings.json` — `Enjin.ApiUrl` already points to the Canary network (`https://platform.canary.enjin.io/graphql`) and `Server.Port` defaults to `3000`. You can leave them as-is for testing.
+ The remaining settings have sensible defaults in `appsettings.json` (for example, `Server.Port` defaults to `3000`), so you can leave them as-is for testing.
4. Launch the server for the first time by running `dotnet run`.
The server will now connect to the Enjin Platform, create a new NFT collection for your game (or reuse an existing one), and create the NFT tokens for the in-game resources. This can take a minute or two on first run while it waits for the on-chain transactions to finalize.
diff --git a/docs/02-guides/01-platform/05-enjin-farmer-sample-game/03-implementation-breakdown.md b/docs/02-guides/01-platform/05-enjin-farmer-sample-game/03-implementation-breakdown.md
index 24288f2..f6055c4 100644
--- a/docs/02-guides/01-platform/05-enjin-farmer-sample-game/03-implementation-breakdown.md
+++ b/docs/02-guides/01-platform/05-enjin-farmer-sample-game/03-implementation-breakdown.md
@@ -27,7 +27,7 @@ Before you begin, please keep the following in mind:
## 🖥️ Game Server
-The game server is a .NET 9 minimal-API application that talks to the Enjin Platform through the [Enjin Platform C# SDK](https://github.com/enjin/platform-csharp-sdk). It serves as the secure bridge between the game client and the platform — it holds the Enjin Platform API token, which the Unity client never sees. The main entry point is the [`Program.cs` file](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Program.cs), where the host, dependency injection, JWT authentication, and on-chain bootstrap are wired up.
+The game server is a .NET 9 minimal-API application that talks to the Enjin Platform through the [Enjin Platform C# SDK](https://github.com/enjin/platform-csharp-sdk). It serves as the secure bridge between the game client and the platform — it holds the Enjin Platform API token, which the Unity client never sees. The main entry point is the [`Program.cs` file](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Program.cs), where the host, dependency injection, JWT authentication, and on-chain bootstrap are wired up.
### Configuration
@@ -38,7 +38,6 @@ The server is configured through `appsettings.json` (defaults) plus an `appsetti
| `Jwt.Secret` | A long, random string used for signing player authentication tokens. |
| `Enjin.ApiToken` | Your API token obtained from the Enjin Platform. |
| `Enjin.DaemonWalletAddress` | The SS58 address of your . This wallet owns the collection, mints the resource tokens, and funds new player wallets. |
-| `Enjin.ApiUrl` | The Enjin Platform API URL. Defaults to `https://platform.canary.enjin.io/graphql` (Canary). Switch to the production endpoint when you ship. |
| `Enjin.Network` / `Enjin.Chain` | The target network and chain. Defaults to `Canary` / `Matrix`. |
| `Server.Port` | The port the server listens on. Defaults to `3000` (the Unity client expects `3000`). |
| `Enjin.CollectionName` | Used to find or reuse an existing collection so a new one isn't created on every run. |
@@ -46,13 +45,13 @@ The server is configured through `appsettings.json` (defaults) plus an `appsetti
| `Enjin.Ss58Prefix` | The prefix used to encode wallet public keys into addresses. `9030` = Canary Matrixchain, `1110` = Enjin Mainnet Matrixchain. |
| `Enjin.DripEnjEnabled` / `Enjin.DripEnjAmount` | Whether to auto-fund each new managed wallet, and how much (default `1` ENJ). |
-The strongly-typed config classes live in [`Services/Options.cs`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/Options.cs), and all the defaults are in [`appsettings.json`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/appsettings.json).
+The strongly-typed config classes live in [`Services/Options.cs`](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Services/Options.cs), and all the defaults are in [`appsettings.json`](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/appsettings.json).
### Server Initialization & Collection Setup
-On startup — before serving any requests — the server runs [`PrepareCollectionAsync`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L80) to make sure the collection and resource tokens exist.
+On startup — before serving any requests — the server runs [`PrepareCollectionAsync`](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Services/EnjinService.cs#L75) to make sure the collection and resource tokens exist.
-1. **Reuse or create the collection:** If no collection ID is stored yet, the server first [queries `GetCollections`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L142) for one owned by the daemon wallet whose `name` attribute matches `Enjin.CollectionName` — this avoids creating duplicates if local state was lost. If none exists, it [creates one](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L170).
+1. **Reuse or create the collection:** If no collection ID is stored yet, the server first [queries `GetCollections`](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Services/EnjinService.cs#L137) for one owned by the daemon wallet whose `name` attribute matches `Enjin.CollectionName` — this avoids creating duplicates if local state was lost. If none exists, it [creates one](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Services/EnjinService.cs#L165).
In v3, every on-chain mutation goes through a single **`CreateTransaction`** mutation: you populate a `TransactionInput` with one of its method fields (`CreateCollection`, `CreateToken`, `MintToken`, `BurnToken`, `TransferToken`, …) and submit it. Collection creation looks like this:
```csharp
@@ -71,7 +70,7 @@ On startup — before serving any requests — the server runs [`PrepareCollecti
var submitted = await CreateTransactionAsync(transaction, signerExternalId: null, ct);
await WaitForFinalizationAsync(submitted.Uuid!, "collection creation", ct);
```
- [`CreateTransactionAsync`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L587) builds the mutation with the SDK's query builders and sends it. The picks the transaction up and signs it automatically:
+ [`CreateTransactionAsync`](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Services/EnjinService.cs#L582) builds the mutation with the SDK's query builders and sends it. The picks the transaction up and signs it automatically:
```csharp
var mutation = new MutationQueryBuilder().WithCreateTransaction(
new TransactionQueryBuilder().WithUuid().WithState(),
@@ -82,9 +81,9 @@ On startup — before serving any requests — the server runs [`PrepareCollecti
var resp = await _client.SendMutation(mutation);
```
-2. **Wait for finalization:** `CreateTransaction` returns a transaction UUID. The server then [polls `GetTransaction`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L613) by that UUID until `State` is `FINALIZED` (it throws if the transaction ends up `FAILED`, `ABANDONED`, or `TIMEOUT`). Since v3 doesn't surface a transaction's emitted events, the new collection's ID is recovered by re-querying `GetCollections` and matching on the `name` attribute.
+2. **Wait for finalization:** `CreateTransaction` returns a transaction UUID. The server then [polls `GetTransaction`](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Services/EnjinService.cs#L608) by that UUID until `State` is `FINALIZED` (it throws if the transaction ends up `FAILED`, `ABANDONED`, or `TIMEOUT`). Since v3 doesn't surface a transaction's emitted events, the new collection's ID is recovered by re-querying `GetCollections` and matching on the `name` attribute.
-3. **Create resource tokens:** For each entry in `Enjin.ResourceTokens`, the server [checks whether the token already exists](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L205) with a `GetToken` query and, if not, [creates it](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L224) with a `CreateToken` input.
+3. **Create resource tokens:** For each entry in `Enjin.ResourceTokens`, the server [checks whether the token already exists](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Services/EnjinService.cs#L200) with a `GetToken` query and, if not, [creates it](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Services/EnjinService.cs#L219) with a `CreateToken` input.
4. **Persist:** The resolved collection ID is written to `state.json` so subsequent runs reuse it instead of re-creating anything.
@@ -92,24 +91,24 @@ After this, the server starts listening and logs the collection ID and `Server l
### Managed Wallets & Addresses
-Each player gets one [managed wallet](/02-guides/01-platform/02-managing-users/03-using-managed-wallets.md), keyed by their email as the `externalId`. The server [ensures it exists](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L279) by calling the `CreateManagedWallet` mutation; because the platform provisions wallets asynchronously, the server then polls `GetManagedWallet` until the wallet is queryable. The platform returns the wallet's **public key** as hex, which the server [SS58-encodes locally](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L387) (using `Enjin.Ss58Prefix`) into the address used as a mint recipient and holder. Each new wallet is also [dripped a little ENJ](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L318) from the daemon so it can pay transaction fees.
+Each player gets one [managed wallet](/02-guides/01-platform/02-managing-users/03-using-managed-wallets.md), keyed by their email as the `externalId`. The server [ensures it exists](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Services/EnjinService.cs#L274) by calling the `CreateManagedWallet` mutation; because the platform provisions wallets asynchronously, the server then polls `GetManagedWallet` until the wallet is queryable. The platform returns the wallet's **public key** as hex, which the server [SS58-encodes locally](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Services/EnjinService.cs#L382) (using `Enjin.Ss58Prefix`) into the address used as a mint recipient and holder. Each new wallet is also [dripped a little ENJ](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Services/EnjinService.cs#L313) from the daemon so it can pay transaction fees.
### API Endpoints
-The server exposes several endpoints to handle game actions. The `wallet` and `token` route groups require a valid JWT (`.RequireAuthorization()`, backed by the [JWT bearer configuration in `Program.cs`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Program.cs#L70)). Each protected handler reads the player's `email` claim and uses it as the `externalId` when talking to the platform.
+The server exposes several endpoints to handle game actions. The `wallet` and `token` route groups require a valid JWT (`.RequireAuthorization()`, backed by the [JWT bearer configuration in `Program.cs`](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Program.cs#L70)). Each protected handler reads the player's `email` claim and uses it as the `externalId` when talking to the platform.
#### Authentication
- * [`GET /api/auth/health-check`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Endpoints/AuthEndpoints.cs#L16): A simple endpoint to verify that the server is online.
- * [`POST /api/auth/register`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Endpoints/AuthEndpoints.cs#L23): Registers a new player **or** logs in an existing one (the client uses this single endpoint for both), returning a JWT. On first registration it also [provisions the player's managed wallet](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Endpoints/AuthEndpoints.cs#L45) and drips it some cENJ, using the player's [email address as the unique `externalId`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/AuthService.cs#L29).
+ * [`GET /api/auth/health-check`](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Endpoints/AuthEndpoints.cs#L16): A simple endpoint to verify that the server is online.
+ * [`POST /api/auth/register`](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Endpoints/AuthEndpoints.cs#L23): Registers a new player **or** logs in an existing one (the client uses this single endpoint for both), returning a JWT. On first registration it also [provisions the player's managed wallet](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Endpoints/AuthEndpoints.cs#L45) and drips it some cENJ, using the player's [email address as the unique `externalId`](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Services/AuthService.cs#L29).
#### Wallet Management
- * [`GET /api/wallet/get-tokens`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Endpoints/WalletEndpoints.cs#L16): Retrieves the player's managed wallet and the resource-token balances it holds. It [looks up the wallet](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L400) via `GetManagedWallet`, then queries `GetToken` for each resource token and filters the holder list down to the player's address.
+ * [`GET /api/wallet/get-tokens`](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Endpoints/WalletEndpoints.cs#L16): Retrieves the player's managed wallet and the resource-token balances it holds. It [looks up the wallet](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Services/EnjinService.cs#L395) via `GetManagedWallet`, then queries `GetToken` for each resource token and filters the holder list down to the player's address.
#### Token Actions
- * [`POST /api/token/mint`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Endpoints/TokenEndpoints.cs#L14): Mints a token into the player's managed wallet via [`MintTokenAsync`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L512). The daemon signs it.
+ * [`POST /api/token/mint`](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Endpoints/TokenEndpoints.cs#L14): Mints a token into the player's managed wallet via [`MintTokenAsync`](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Services/EnjinService.cs#L507). The daemon signs it.
```csharp
var input = new TransactionInput
{
@@ -122,7 +121,7 @@ The server exposes several endpoints to handle game actions. The `wallet` and `t
},
};
```
- * [`POST /api/token/melt`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Endpoints/TokenEndpoints.cs#L36): Melts (burns) a token from the player's wallet via [`MeltTokenAsync`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L532). Since the burn must come from the player's managed wallet, the call passes the player's email as `signerExternalId` so the daemon signs on that wallet's behalf. ([Learn more about managed wallets here](/02-guides/01-platform/02-managing-users/03-using-managed-wallets.md))
+ * [`POST /api/token/melt`](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Endpoints/TokenEndpoints.cs#L36): Melts (burns) a token from the player's wallet via [`MeltTokenAsync`](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Services/EnjinService.cs#L527). Since the burn must come from the player's managed wallet, the call passes the player's email as `signerExternalId` so the daemon signs on that wallet's behalf. ([Learn more about managed wallets here](/02-guides/01-platform/02-managing-users/03-using-managed-wallets.md))
```csharp
var input = new TransactionInput
{
@@ -134,7 +133,7 @@ The server exposes several endpoints to handle game actions. The `wallet` and `t
},
};
```
- * [`POST /api/token/transfer`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Endpoints/TokenEndpoints.cs#L57): Transfers a token from the player's managed wallet to another SS58 address via [`TransferTokenAsync`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Services/EnjinService.cs#L551), again signed via the player's `signerExternalId`.
+ * [`POST /api/token/transfer`](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Endpoints/TokenEndpoints.cs#L57): Transfers a token from the player's managed wallet to another SS58 address via [`TransferTokenAsync`](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Services/EnjinService.cs#L546), again signed via the player's `signerExternalId`.
```csharp
var input = new TransactionInput
{
@@ -150,7 +149,7 @@ The server exposes several endpoints to handle game actions. The `wallet` and `t
#### Setup
- * [`GET /api/setup/collection-id`](https://github.com/enjin/platform-sample-game-server/blob/5cb7ba89c3c0fcfe73b481ceacf5035b9b480ca8/Endpoints/SetupEndpoints.cs#L26): Returns the collection ID the server bootstrapped. This is called once by the Unity Editor's "Stamp Collection ID" menu (see [Setup Guide → Step 4](/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md#1-stamp-the-collection-id-onto-the-nft-items)) to write the ID onto the client's NFT item assets; the running game never calls it.
+ * [`GET /api/setup/collection-id`](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Endpoints/SetupEndpoints.cs#L26): Returns the collection ID the server bootstrapped. This is called once by the Unity Editor's "Stamp Collection ID" menu (see [Setup Guide → Step 4](/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md#1-stamp-the-collection-id-onto-the-nft-items)) to write the ID onto the client's NFT item assets; the running game never calls it.
-----
From d8f977c6aad42981b7642df9520a2fb7cbbc5e6a Mon Sep 17 00:00:00 2001
From: Etay Matzliah
Date: Wed, 24 Jun 2026 22:33:56 +0300
Subject: [PATCH 3/8] Farmer setup: clarify .NET SDK note and add NuGet restore
troubleshooting
.NET 10 (or any SDK newer than 9) can build the net9.0 server, so don't
imply only 9.x works. Also note that first run restores dependencies
(including the now-public Enjin.Platform.Sdk) from NuGet and add a pointer
for the NU1100 "unable to resolve" failure (nuget.org source disabled/
unreachable), which is the actual blocker users hit, not a missing SDK.
Co-Authored-By: Claude Opus 4.8 (1M context)
---
.../05-enjin-farmer-sample-game/01-setup-guide.md | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md b/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md
index 865d148..5c48bfa 100644
--- a/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md
+++ b/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md
@@ -86,8 +86,10 @@ For a detailed guide — including Docker, AWS, importing an existing seed, and
Now, let's set up the backend server that powers the game's NFT features.
-:::warning You need the .NET 9 SDK
-The server runs on .NET, so the **.NET 9 SDK** (not just the runtime) must be installed. Confirm it by running `dotnet --list-sdks` — you should see a `9.x.x` entry. If the command isn't found or lists nothing, install the [.NET 9 SDK](https://dotnet.microsoft.com/download), then open a **new** terminal so your `PATH` picks it up.
+:::warning Before you run: .NET SDK + internet
+The server targets .NET 9, so you need the **.NET 9 SDK** (the SDK, not just the runtime). A newer SDK such as .NET 10 also works — it just downloads the .NET 9 build components on first run. Confirm an SDK is installed with `dotnet --list-sdks`; if the command isn't found or lists nothing, install the [.NET 9 SDK](https://dotnet.microsoft.com/download) and open a **new** terminal so your `PATH` picks it up.
+
+The first `dotnet run` restores the server's dependencies — including the [Enjin Platform C# SDK](https://www.nuget.org/packages/Enjin.Platform.Sdk) — from NuGet, so you need internet access. If restore fails with `NU1100: Unable to resolve …`, your machine can't reach the NuGet feed: run `dotnet nuget list source` and make sure `nuget.org` is listed and **enabled**.
:::
1. Navigate into the game server directory you cloned: `cd platform-sample-game-server`.
From 59feb2a81240ad67c0012f651d6dcc14cf088c14 Mon Sep 17 00:00:00 2001
From: Etay Matzliah
Date: Wed, 24 Jun 2026 22:38:56 +0300
Subject: [PATCH 4/8] Farmer setup: give explicit nuget add-source command for
NU1100
Restore can fail with "No sources found" when a machine has no NuGet
source configured at all (not just disabled). Add the exact
`dotnet nuget add source` command so users can fix it directly.
Co-Authored-By: Claude Opus 4.8 (1M context)
---
.../05-enjin-farmer-sample-game/01-setup-guide.md | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md b/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md
index 5c48bfa..f66f053 100644
--- a/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md
+++ b/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md
@@ -89,7 +89,11 @@ Now, let's set up the backend server that powers the game's NFT features.
:::warning Before you run: .NET SDK + internet
The server targets .NET 9, so you need the **.NET 9 SDK** (the SDK, not just the runtime). A newer SDK such as .NET 10 also works — it just downloads the .NET 9 build components on first run. Confirm an SDK is installed with `dotnet --list-sdks`; if the command isn't found or lists nothing, install the [.NET 9 SDK](https://dotnet.microsoft.com/download) and open a **new** terminal so your `PATH` picks it up.
-The first `dotnet run` restores the server's dependencies — including the [Enjin Platform C# SDK](https://www.nuget.org/packages/Enjin.Platform.Sdk) — from NuGet, so you need internet access. If restore fails with `NU1100: Unable to resolve …`, your machine can't reach the NuGet feed: run `dotnet nuget list source` and make sure `nuget.org` is listed and **enabled**.
+The first `dotnet run` restores the server's dependencies — including the [Enjin Platform C# SDK](https://www.nuget.org/packages/Enjin.Platform.Sdk) — from NuGet, so you need internet access. If restore fails with `NU1100: Unable to resolve …`, your machine has no usable NuGet source. Run `dotnet nuget list source`: if it says `No sources found` (or `nuget.org` is `[Disabled]`), add it with:
+
+```bash
+dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org
+```
:::
1. Navigate into the game server directory you cloned: `cd platform-sample-game-server`.
From de2586e5f4638ed54689e31dae27bae4c11484c7 Mon Sep 17 00:00:00 2001
From: Etay Matzliah
Date: Wed, 24 Jun 2026 22:43:39 +0300
Subject: [PATCH 5/8] Farmer setup: correct .NET 9 runtime requirement
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
A newer SDK (e.g. .NET 10) can build the net9.0 server but can't run it
without the .NET 9 runtime — dotnet run fails with "You must install or
update .NET ... version '9.0.0'". Recommend installing the .NET 9 SDK
(bundles the runtime); note the --roll-forward Major alternative.
Co-Authored-By: Claude Opus 4.8 (1M context)
---
.../01-platform/05-enjin-farmer-sample-game/01-setup-guide.md | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md b/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md
index f66f053..fed9108 100644
--- a/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md
+++ b/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md
@@ -87,7 +87,9 @@ For a detailed guide — including Docker, AWS, importing an existing seed, and
Now, let's set up the backend server that powers the game's NFT features.
:::warning Before you run: .NET SDK + internet
-The server targets .NET 9, so you need the **.NET 9 SDK** (the SDK, not just the runtime). A newer SDK such as .NET 10 also works — it just downloads the .NET 9 build components on first run. Confirm an SDK is installed with `dotnet --list-sdks`; if the command isn't found or lists nothing, install the [.NET 9 SDK](https://dotnet.microsoft.com/download) and open a **new** terminal so your `PATH` picks it up.
+The server targets .NET 9, so install the **[.NET 9 SDK](https://dotnet.microsoft.com/download/dotnet/9.0)** — it provides both the build tools and the .NET 9 runtime the server runs on. After installing, open a **new** terminal and confirm with `dotnet --list-sdks` (you should see a `9.x` entry).
+
+If you only have a newer SDK such as .NET 10, the build will succeed but `dotnet run` fails with `You must install or update .NET … version '9.0.0'`, because the .NET 9 **runtime** is missing. Either install the .NET 9 SDK (simplest), or run on the newer runtime with `dotnet run --roll-forward Major`.
The first `dotnet run` restores the server's dependencies — including the [Enjin Platform C# SDK](https://www.nuget.org/packages/Enjin.Platform.Sdk) — from NuGet, so you need internet access. If restore fails with `NU1100: Unable to resolve …`, your machine has no usable NuGet source. Run `dotnet nuget list source`: if it says `No sources found` (or `nuget.org` is `[Disabled]`), add it with:
From f12916e68eb827cb4c08c3d43d81c41baf4bc2f8 Mon Sep 17 00:00:00 2001
From: Etay Matzliah
Date: Wed, 24 Jun 2026 23:08:39 +0300
Subject: [PATCH 6/8] Farmer setup: note Windows firewall prompt; fix
server-ready log line
Add a note that Windows may prompt to allow network access on first
listen (click Allow; no restart needed). Also correct the "ready" signal
to the actual Kestrel output (Now listening on: http://[::]:3000 /
Application started) instead of the assumed line.
Co-Authored-By: Claude Opus 4.8 (1M context)
---
.../05-enjin-farmer-sample-game/01-setup-guide.md | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md b/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md
index fed9108..e795d17 100644
--- a/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md
+++ b/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md
@@ -111,7 +111,11 @@ dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org
The server will now connect to the Enjin Platform, create a new NFT collection for your game (or reuse an existing one), and create the NFT tokens for the in-game resources. This can take a minute or two on first run while it waits for the on-chain transactions to finalize.
:::info **Important**
-The server stores the **Collection ID** it bootstraps in a `state.json` file and reuses it on every restart, so you no longer need to copy it by hand — you'll stamp it onto the game client automatically in [Step 4](#1-stamp-the-collection-id-onto-the-nft-items). When you see `Server listening on http://0.0.0.0:3000` in the logs, the server is ready.
+The server stores the **Collection ID** it bootstraps in a `state.json` file and reuses it on every restart, so you no longer need to copy it by hand — you'll stamp it onto the game client automatically in [Step 4](#1-stamp-the-collection-id-onto-the-nft-items). When you see `Now listening on: http://[::]:3000` (and `Application started`) in the logs, the server is ready.
+:::
+
+:::note Windows firewall prompt
+On Windows, the first time the server starts listening you may get a prompt to allow network access — click **Allow**. You don't need to restart the server afterwards; it keeps running and works right away.
:::
Keep the server and the Wallet Daemon running in the background.
From b21bd889dfdf2f0ae5766aec34460df92fe5a82c Mon Sep 17 00:00:00 2001
From: Etay Matzliah
Date: Wed, 24 Jun 2026 23:12:01 +0300
Subject: [PATCH 7/8] Farmer setup: point prerequisite .NET link directly at
9.0
The generic download page surfaces the latest SDK (.NET 10), which builds
but can't run the net9.0 server. Link straight to the 9.0 page, matching
the Step 3 warning.
Co-Authored-By: Claude Opus 4.8 (1M context)
---
.../01-platform/05-enjin-farmer-sample-game/01-setup-guide.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md b/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md
index e795d17..c3a1f7c 100644
--- a/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md
+++ b/docs/02-guides/01-platform/05-enjin-farmer-sample-game/01-setup-guide.md
@@ -27,7 +27,7 @@ The project consists of four main components that work together:
Before you begin, make sure you have the following installed:
- ✅ **Unity Hub** with **Unity Editor version `6000.0.24f1`**.
- - ✅ The **[.NET 9 SDK](https://dotnet.microsoft.com/download)** for running the game server.
+ - ✅ The **[.NET 9 SDK](https://dotnet.microsoft.com/download/dotnet/9.0)** for running the game server.
- ✅ **Git** for cloning the repositories.
- ✅ An **Enjin Platform account**. If you don't have one, you can create it [here](https://platform.beta.enjin.io/).
- ✅ Some cENJ tokens (can be acquired from the [built-in Canary faucet](/01-getting-started/04-using-the-enjin-platform.md#canary-faucet) in the Platform UI)
From 6085091fac46babe5f361b434ee1910eb3b637d4 Mon Sep 17 00:00:00 2001
From: Etay Matzliah
Date: Wed, 24 Jun 2026 23:31:20 +0300
Subject: [PATCH 8/8] Farmer breakdown: reframe collection-ID lookup around
events guidance
Drop the "v3 doesn't surface events" framing. State that the ID is
recovered via GetCollections, and point readers to WebSocket Events for
the production approach (listen for the event). Keeps the availability
detail on the events page so only that page needs updating later.
Co-Authored-By: Claude Opus 4.8 (1M context)
---
.../05-enjin-farmer-sample-game/03-implementation-breakdown.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/02-guides/01-platform/05-enjin-farmer-sample-game/03-implementation-breakdown.md b/docs/02-guides/01-platform/05-enjin-farmer-sample-game/03-implementation-breakdown.md
index f6055c4..066a716 100644
--- a/docs/02-guides/01-platform/05-enjin-farmer-sample-game/03-implementation-breakdown.md
+++ b/docs/02-guides/01-platform/05-enjin-farmer-sample-game/03-implementation-breakdown.md
@@ -81,7 +81,7 @@ On startup — before serving any requests — the server runs [`PrepareCollecti
var resp = await _client.SendMutation(mutation);
```
-2. **Wait for finalization:** `CreateTransaction` returns a transaction UUID. The server then [polls `GetTransaction`](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Services/EnjinService.cs#L608) by that UUID until `State` is `FINALIZED` (it throws if the transaction ends up `FAILED`, `ABANDONED`, or `TIMEOUT`). Since v3 doesn't surface a transaction's emitted events, the new collection's ID is recovered by re-querying `GetCollections` and matching on the `name` attribute.
+2. **Wait for finalization:** `CreateTransaction` returns a transaction UUID. The server then [polls `GetTransaction`](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Services/EnjinService.cs#L608) by that UUID until `State` is `FINALIZED` (it throws if the transaction ends up `FAILED`, `ABANDONED`, or `TIMEOUT`). The new collection's ID is then recovered by querying `GetCollections` and matching on the `name` attribute. In a real-world application you'd instead listen for the collection-creation event rather than query for it — see [WebSocket Events](/03-api-reference/03-websocket-events.md).
3. **Create resource tokens:** For each entry in `Enjin.ResourceTokens`, the server [checks whether the token already exists](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Services/EnjinService.cs#L200) with a `GetToken` query and, if not, [creates it](https://github.com/enjin/platform-sample-game-server/blob/64949d25394526ef478b81c06a5d1e36375e455e/Services/EnjinService.cs#L219) with a `CreateToken` input.