Enjin Farmer: Overview & Server Setup
Welcome! The Enjin Farmer is a sample farming game that shows how to integrate Enjin's NFT technology into a real game. You plant seeds, harvest crops, and collect resources — some of which are minted as NFTs directly to your in-game wallet. You can then view those NFTs in your inventory, Melt
them, or transfer them to an external wallet.The sample comes in two flavors that share the same backend:
- 🟦 Unity client — built with Unity.
- 🟧 Godot client — built with Godot.
Both talk to the same .NET game server, so this page covers everything common to both: the architecture, prerequisites, and how to stand up the server, Enjin Platform, and Wallet Daemon. Once the server is running, head to the page for your engine to set up and run the client.
System Architecture
The project consists of four main components that work together:
- Game Client (Unity or Godot): The game itself, where you play and interact with items.
- Game Server (.NET): A backend API, built on the Enjin Platform C# SDK, that the game client communicates with to handle all NFT-related actions like minting and transferring.
- Enjin Platform: 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.
💡 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.
- Polling, not subscriptions: The Enjin Platform API doesn't yet expose WebSocket events, so after submitting a transaction the server polls the
GetTransactionquery 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 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.
Prerequisites
Before you begin, make sure you have the following. These are the requirements for the shared server; each engine page lists the extra tooling its client needs (Unity Hub, or the Godot editor).
- ✅ The .NET 9 SDK 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.
- ✅ Some cENJ tokens (can be acquired from the built-in Canary faucet in the Platform UI).
Step 1: Download the Server & Wallet Daemon
-
Clone the Game Server: Open a terminal and run:
git clone https://github.com/enjin/platform-sample-game-server.git -
Download the Wallet Daemon: Download the prebuilt daemon for your operating system from https://enj.in/daemon and extract it into a dedicated directory.
You'll clone your engine's game client (Unity or Godot) on its own setup page, after the server is running.
Step 2: Configure Enjin Services
Next, you'll set up your Enjin Platform account and the Wallet Daemon.
Enjin Platform
- Log in to your Enjin Platform account.
- Head over to your account settings page.
- Navigate to the Daemon Wallet section and create a new API Token.
- Copy the API Token; you will need this in the next step.
Wallet Daemon
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.
- In the daemon directory you extracted, copy the
.env.examplefile to.env. - Open
.envand 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.
- Start the daemon —
./wallet-daemon(or.\wallet-daemon.exeon Windows). On first run it generates a new wallet, writes the encrypted seed towallet.seed, and prints an SS58 address for each network. - From the printed addresses, copy the Canary Matrixchain address — that's the network this sample uses. You'll need it in the next step.
For a detailed guide — including Docker, AWS, importing an existing seed, and backup guidance — see the Wallet Daemon documentation.
Step 3: Configure and Run the Game Server
Now, let's set up the backend server that powers the game's NFT features.
The server targets .NET 9, so install the .NET 9 SDK — 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 — 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:
dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org
-
Navigate into the game server directory you cloned:
cd platform-sample-game-server. -
Copy the
appsettings.Sample.jsonfile and rename the copy toappsettings.Local.json(this file is gitignored, so your secrets stay out of version control). -
Open
appsettings.Local.jsonand 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.Portdefaults to3000), so you can leave them as-is for testing. -
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.
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 when you set up your engine's client. When you see Now listening on: http://[::]:3000 (and Application started) in the logs, the server is ready.
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.
Server Implementation Breakdown
The game server is a .NET 9 minimal-API application that talks to the Enjin Platform through the Enjin Platform C# SDK. It serves as the secure bridge between the game client and the platform — it holds the Enjin Platform API token, which the game client never sees. The main entry point is the Program.cs file, where the host, dependency injection, JWT authentication, and on-chain bootstrap are wired up.
Configuration
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.
| Setting | Description |
|---|---|
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 Wallet Daemon. 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 (both the Unity and Godot clients default to 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). Both the Unity and Godot clients ship matching item 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, and all the defaults are in appsettings.json.
Server Initialization & Collection Setup
On startup — before serving any requests — the server runs PrepareCollectionAsync to make sure the collection and resource tokens exist.
-
Reuse or create the collection: If no collection ID is stored yet, the server first queries
GetCollectionsfor one owned by the daemon wallet whosenameattribute matchesEnjin.CollectionName— this avoids creating duplicates if local state was lost. If none exists, it creates one.In v3, every on-chain mutation goes through a single
CreateTransactionmutation: you populate aTransactionInputwith one of its method fields (CreateCollection,CreateToken,MintToken,BurnToken,TransferToken, …) and submit it. Collection creation looks like this:var transaction = new TransactionInput
{
CreateCollection = new CreateCollectionInput
{
Attributes = new List<AttributeInput>
{
new() { Key = "name", Value = _opts.CollectionName },
// banner_image, media, ...
},
},
};
var submitted = await CreateTransactionAsync(transaction, signerExternalId: null, ct);
await WaitForFinalizationAsync(submitted.Uuid!, "collection creation", ct);
picks the transaction up and signs it automatically:CreateTransactionAsyncbuilds the mutation with the SDK's query builders and sends it. The Wallet Daemonvar mutation = new MutationQueryBuilder().WithCreateTransaction(
new TransactionQueryBuilder().WithUuid().WithState(),
_network, _chain,
transaction: input,
signerExternalId: signerExternalId);
var resp = await _client.SendMutation(mutation); -
Wait for finalization:
CreateTransactionreturns a transaction UUID. The server then pollsGetTransactionby that UUID untilStateisFINALIZED(it throws if the transaction ends upFAILED,ABANDONED, orTIMEOUT). The new collection's ID is then recovered by queryingGetCollectionsand matching on thenameattribute. In a real-world application you'd instead listen for the collection-creation event rather than query for it — see WebSocket Events. -
Create resource tokens: For each entry in
Enjin.ResourceTokens, the server checks whether the token already exists with aGetTokenquery and, if not, creates it with aCreateTokeninput. -
Persist: The resolved collection ID is written to
state.jsonso 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, keyed by their email as the externalId. The server ensures it exists 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 (using Enjin.Ss58Prefix) into the address used as a mint recipient and holder. Each new wallet is also dripped a little ENJ 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). 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: A simple endpoint to verify that the server is online.POST /api/auth/register: 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 and drips it some cENJ, using the player's email address as the uniqueexternalId.
Wallet Management
GET /api/wallet/get-tokens: Retrieves the player's managed wallet and the resource-token balances it holds. It looks up the wallet viaGetManagedWallet, then queriesGetTokenfor each resource token and filters the holder list down to the player's address.
Token Actions
POST /api/token/mint: Mints a token into the player's managed wallet viaMintTokenAsync. The daemon signs it.var input = new TransactionInput
{
MintToken = new MintTokenInput
{
Recipient = recipientAddress,
CollectionId = RequireCollectionId(),
TokenId = tokenId,
Amount = amount,
},
};POST /api/token/melt: Melts (burns) a token from the player's wallet viaMeltTokenAsync. Since the burn must come from the player's managed wallet, the call passes the player's email assignerExternalIdso the daemon signs on that wallet's behalf. (Learn more about managed wallets here)var input = new TransactionInput
{
BurnToken = new BurnTokenInput
{
CollectionId = RequireCollectionId(),
TokenId = tokenId,
Amount = amount,
},
};POST /api/token/transfer: Transfers a token from the player's managed wallet to another SS58 address viaTransferTokenAsync, again signed via the player'ssignerExternalId.var input = new TransactionInput
{
TransferToken = new TransferTokenInput
{
Recipient = recipientAddress,
CollectionId = RequireCollectionId(),
TokenId = tokenId,
Amount = amount,
},
};
Setup
GET /api/setup/collection-id: Returns the collection ID the server bootstrapped. This is called once by the engine client's "Stamp Collection ID" tool to write the ID onto the client's NFT item assets; the running game never calls it.
Next: set up your client
The server, Enjin Platform, and Wallet Daemon are now configured. Keep the server and the Wallet Daemon running, then continue to the page for your engine: