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..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 @@ -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/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) @@ -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,22 +86,36 @@ 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 Before you run: .NET SDK + internet +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: + +```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`. -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 daemon's **Canary Matrixchain** address you copied in the previous step. + + 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. :::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 `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. @@ -109,18 +131,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 - - 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. -

- -

+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. + + - 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 +171,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 +188,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..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 @@ -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,145 @@ 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/64949d25394526ef478b81c06a5d1e36375e455e/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.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/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, 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/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/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 + 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/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(), + _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/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. + +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/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` 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/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/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/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 - * [`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/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/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/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 + { + 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/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 + { + 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/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 + { + 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/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. + ----- ## 🎮 Unity Game @@ -148,17 +162,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 +181,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).