Using Unity & PlayFab

This tutorial will teach you how to set up a complete gaming backend platform for Web3 games. We use Unity as our game engine, Microsoft Azure PlayFab as our gaming backend, and Moralis to enable all the Web3 magic! You'll also learn how to use Web3 authentication and how to retrieve a user's native balance, a list of ERC-20 tokens, and the first ten NFTs they own using the Web3 API.

The Steps We Will Take

  1. Set up a Moralis account
  2. Set up Microsoft Azure PlayFab (free plan)
  3. Set up Azure Functions
  4. Create Azure Functions with Visual Studio Code
  5. Deploy Azure Functions with Visual Studio Code
  6. Set up Unity and connect Microsoft Azure PlayFab

Set Up a Moralis Account

This section describes how to set up your Moralis account and find the Web3 API you need to integrate. Your Moralis account provides access to all Moralis Web3 functionality.
  1. Create a Moralis account if you don't already have one.
  2. Go to Web3 APIs and copy and save the API key v3. You will need this when setting up Azure Functions.
860860

Set Up Microsoft Azure PlayFab (free plan)

An active PlayFab account is required to use the functionality provided by PlayFab. Please note that a free plan is available. This section describes how to set up a PlayFab account and configure your first application.

πŸ“˜

What is Microsoft Azure PlayFab?

PlayFab is a complete backend platform for live games with managed game services. PlayFab backend services reduce the barriers to launch for game developers, offering both large and small studios cost-effective development solutions that scale with their games and help them engage, retain, and monetize players. PlayFab enables developers to use the intelligent cloud to build and operate games, analyze gaming data, and improve overall gaming experiences.

Learn more

  1. Create a PlayFab Account.
  2. Create a new title on PlayFab:
895895
  1. Open your new title and go to Title settings:
605605
  1. Go to the API Features tab and write down the Title ID. You will need this when setting up Azure Functions.
  2. Go to the Secret Keys tab and write down the secret key. You will need this when setting up Azure Functions.

Set Up Azure Functions

To run Moralis on the backend of PlayFab, we need to use Azure Functions. Azure Functions integrates natively with PlayFab; however, Azure Functions is a separate product and requires an account separate from PlayFab.

πŸ“˜

What is Azure Functions?

Azure Functions provides serverless compute. You can use Azure Functions to build web APIs, respond to database changes, process IoT streams, manage message queues, and more. PlayFab uses Azure Functions to make it possible to use Moralis on top of the PlayFab backend.

More info

  1. Create a free Azure account.
  2. Create a Microsoft Customer Agreement subscription.
  3. Search for Function App and select it.
605605
  1. Create a new Function App with the following settings:
915915
  1. Select Review + create and then Create.
  2. Open the MoralisAzureFunctions resource:
10691069
  1. Go to Configuration and select New application setting:
820820
  1. Enter MORALIS_API_KEY as the name, and in the value, paste your Moralis API key. Leave Deployment slot setting unchecked and select Ok.
  2. Create a second application setting. Enter MORALIS_AUTHENTICATION_API_URL as the name, and in the value, enter https://authapi.moralis.io/. Leave Deployment slot setting unchecked and select Ok.
  3. Create a third application setting. Enter MORALIS_WEB3_API_URL as the name, and in the value, enter https://deep-index.moralis.io/api/v2. Leave Deployment slot setting unchecked and select Ok.
  4. Create another application setting and enter PLAYFAB_TITLE_ID as the name, and in the value, paste your PlayFab title ID. Leave Deployment slot setting unchecked and select Ok.
  5. Create another application setting and enter PLAYFAB_DEV_SECRET_KEY as the name, and in the value, paste your PlayFab secret key. Leave Deployment slot setting unchecked and select Ok.
  6. Select Save and then Continue.
19991999 10171017

Create Azure Functions with Visual Studio Code

This section walks through coding functions that integrate the Moralis SDK.

Prerequisites

Before you get started, make sure you have the following requirements in place:

  1. .NET 6.0 SDK
  2. Azure Functions Core Tools version 4.x
  3. Visual Studio Code
  4. C# extension for Visual Studio Code
  5. Azure Functions extension for Visual Studio Code

Project Setup

πŸ“˜

The full code for the functions project can be found on GitHub.

  1. Open Visual Studio Code.
  2. Select Azure and Workspace and add a function:
510510
  1. Click on Create new project and select or create a folder. For this example, we will create and select a new folder named example-auth-azure-functions.
  2. For the language, select C#:
619619
  1. Select the .NET runtime you want to use. For this tutorial, we select .NET 6.0 LTS:
618618
  1. Select HTTP trigger as the template:
615615
  1. Provide a root namespace for your project:
613613
  1. For AccessRights, select Function:
612612
  1. Select Open in current window.
  2. You should see a pop-up window indicating missing dependencies; click on Restore:
464464
  1. Open the .csproj file. There will be an ItemGroup element with one or more PackageReference elements. Replace this section with the following code:
<ItemGroup>
    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.1.1"/>
    <PackageReference Include="PlayFabAllSDK" Version="1.127.220718"/>
    <PackageReference Include="PlayFabCloudScriptPlugin" Version="1.53.190627-alpha"/>
    <PackageReference Include="Moralis" Version="2.0.4-beta"/>
</ItemGroup>

Save the updated file. You should see the pop-up indicating missing dependencies; click on Restore.

Main Code File

This section walks through coding our main functions code file. The functions created in this tutorial assume that requests are delivered via PlayFab.
  1. When we created our function, it created ChallengeRequest.cs. For this tutorial, we will create two functions and will put them in the same file. Rename the ChallengeRequest.cs file and class to MoralisPlayFab.
  2. In the MoralisPlayFab.cs file, replace the using statements with these:
using System;
using System.IO;
using System.Threading.Tasks;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using PlayFab.ServerModels;
using Moralis.Network;
using Moralis.AuthApi.Models;
using Moralis.AuthApi.Interfaces;
using Newtonsoft.Json;
  1. In the MoralisPlayFab class, create variables that provide the application settings defined above.
private static string AuthenticationApiUrl = Environment.GetEnvironmentVariable("MORALIS_AUTHENTICATION_API_URL", EnvironmentVariableTarget.Process);
private static string Web3ApiUrl = Environment.GetEnvironmentVariable("MORALIS_WEB3_API_URL", EnvironmentVariableTarget.Process);
private static string ApiKey = Environment.GetEnvironmentVariable("MORALIS_API_KEY", EnvironmentVariableTarget.Process);
  1. Change the method name from Run to ChallengeRequest and remove the get, parameter of the HttpTrigger. Copy the ChallengeRequest method and rename it to ChallengeVerify. Your MoralisPlayFab .cs file should look like this:
using System;
using System.IO;
using System.Threading.Tasks;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using PlayFab.ServerModels;
using Moralis.Network;
using Moralis.AuthApi.Models;
using Moralis.AuthApi.Interfaces;
using Newtonsoft.Json;

namespace PlayFab.AzureFunctions
{
    public static class MoralisPlayFab
    {        
        private static string AuthenticationApiUrl = Environment.GetEnvironmentVariable("MORALIS_AUTHENTICATION_API_URL", EnvironmentVariableTarget.Process);
        private static string Web3ApiUrl = Environment.GetEnvironmentVariable("MORALIS_WEB3_API_URL", EnvironmentVariableTarget.Process);
        private static string ApiKey = Environment.GetEnvironmentVariable("MORALIS_API_KEY", EnvironmentVariableTarget.Process);

        [FunctionName("ChallengeRequest")]
        public static async Task<IActionResult> ChallengeRequest (
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
            ILogger log)
        {
        }
        
        [FunctionName("ChallengeVerify")]
        public static async Task<IActionResult> ChallengeVerify (
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
            ILogger log)
        {
        }
    }
}
  1. Add the following code to each method:
// Create the function execution's context through the request
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
// Deserialize Playfab context
dynamic context = JsonConvert.DeserializeObject(requestBody);
var args = context.FunctionArgument;

This code converts the data passed in the request into a form that we can use in the code. Note that the context variable holds a PlayFab context; we will use this later in the ChallengeVerify method.

ChallengeRequest Method

This section walks through coding the ChallengeRequest method.
  1. In the ChallengeRequest method, add code to define the address and chainId and populate these from the request args:
// Get the address from the request
dynamic address = null;
if (args != null && args["address"] != null)
{
    address = args["address"];
}

// Get the chainid from the request
dynamic chainid = null;
if (args != null && args["chainid"] != null)
{
    chainid = args["chainid"];
}
  1. Add code to initialize and define a Moralis authentication API object:
// Connect with the Moralis Authentication Server
Moralis.AuthApi.MoralisAuthApiClient.Initialize(AuthenticationApiUrl, ApiKey);
IAuthClientApi AuthenticationApi = Moralis.AuthApi.MoralisAuthApiClient.AuthenticationApi;
  1. Add and populate a ChallengeRequestDto object. This object is described in detail here. Populate this object as appropriate for your application.
// Create the authentication message and send it back to the client
// Resources must be RFC 3986 URIs
// More info here: https://eips.ethereum.org/EIPS/eip-4361#message-field-descriptions
ChallengeRequestDto request = new ChallengeRequestDto()
{
    Address = address,
    ChainId = (long)chainid,
    Domain = "moralis.io",
    ExpirationTime = DateTime.UtcNow.AddMinutes(60),
    NotBefore = DateTime.UtcNow,
    Resources = new string[] { "https://docs.moralis.io/" },
    Timeout = 120,
    Statement = "Please confirm",
    Uri = "https://moralis.io/"
};
  1. Finally, call the Challenge operation of the Moralis AuthenticationApi endpoint. Return the ChallengeResponseDto object that is returned:
ChallengeResponseDto response = await AuthenticationApi.AuthEndpoint.Challenge(request, ChainNetworkType.evm);

return new OkObjectResult(response.Message);

Your complete ChallengeRequest method should look similar to this:

[FunctionName("ChallengeRequest")]
public static async Task<dynamic> ChallengeRequest(
    [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req, ILogger log)
{
    // Create the function execution's context through the request
    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
    // Deserialize Playfab context
    dynamic context = JsonConvert.DeserializeObject(requestBody);
    var args = context.FunctionArgument;

    // Get the address from the request
    dynamic address = null;
    if (args != null && args["address"] != null)
    {
        address = args["address"];
    }

    // Get the chainid from the request
    dynamic chainid = null;
    if (args != null && args["chainid"] != null)
    {
        chainid = args["chainid"];
    }

    try
    {
        // Connect with the Moralis Authentication Server
        Moralis.AuthApi.MoralisAuthApiClient.Initialize(AuthenticationApiUrl, ApiKey);
        IAuthClientApi AuthenticationApi = Moralis.AuthApi.MoralisAuthApiClient.AuthenticationApi;

        // Create the authentication message and send it back to the client
        // Resources must be RFC 3986 URIs
        // More info here: https://eips.ethereum.org/EIPS/eip-4361#message-field-descriptions
        ChallengeRequestDto request = new ChallengeRequestDto()
        {
            Address = address,
            ChainId = (long)chainid,
            Domain = "moralis.io",
            ExpirationTime = DateTime.UtcNow.AddMinutes(60),
            NotBefore = DateTime.UtcNow,
            Resources = new string[] { "https://docs.moralis.io/" },
            Timeout = 120,
            Statement = "Please confirm",
            Uri = "https://moralis.io/"
        };

        ChallengeResponseDto response = await AuthenticationApi.AuthEndpoint.Challenge(request, ChainNetworkType.evm);
        
        return new OkObjectResult(response.Message);
    }
    catch (ApiException aexp)
    {
        log.LogDebug($"aexp.Message: {aexp.ToString()}");
        return new BadRequestObjectResult(aexp.Message);
    }
    catch (Exception exp)
    {
        log.LogDebug($"exp.Message: {exp.ToString()}");
        return new BadRequestObjectResult(exp.Message);
    }
}

ChallengeVerify Method

This section walks through coding the ChallengeVerify method. 1. In the `ChallengeVerify` method, add code to define `message` and `signature` and populate these from the request args:
/// Get the message from the request
dynamic message = null;
if (args != null && args["message"] != null)
{
    message = args["message"];
}

// Get the signature from the request
dynamic signature = null;
if (args != null && args["signature"] != null)
{
    signature = args["signature"];
}
  1. A CompleteChallengeResponseDto object will be needed. Go ahead and add a default definition for the response object:
CompleteChallengeResponseDto response = null;
  1. Add code to initialize and define a Moralis authentication API object:
// Connect with the Moralis Authentication Server
Moralis.AuthApi.MoralisAuthApiClient.Initialize(AuthenticationApiUrl, ApiKey);
IAuthClientApi AuthenticationApi = Moralis.AuthApi.MoralisAuthApiClient.AuthenticationApi;
  1. Create a CompleteChallangeRequestDto object populated with the message and signature and use it to call the CompleteChallenge operation of the Moralis authentication API endpoint. Use the response object created earlier to capture the response of this call:
// Create the authentication message and send it back to the client
CompleteChallengeRequestDto request = new CompleteChallengeRequestDto()
{
    Message = message,
    Signature = signature
};

response = await AuthenticationApi.AuthEndpoint.CompleteChallenge(request, ChainNetworkType.evm);
  1. If the message and signature were successfully verified by Moralis, add the user's wallet address, chain ID, and the Moralis profile ID for this user to the user's PlayFab record. If this update receives, return the updated record to the caller:
// Get the setting from our Azure config and connect with the PlayFab API
var settings = new PlayFabApiSettings
{
    TitleId = Environment.GetEnvironmentVariable("PLAYFAB_TITLE_ID", EnvironmentVariableTarget.Process),
    DeveloperSecretKey = Environment.GetEnvironmentVariable("PLAYFAB_DEV_SECRET_KEY", EnvironmentVariableTarget.Process)
};

var serverApi = new PlayFabServerInstanceAPI(settings);

// Update the user read-only data with the validated data and return the reponse to the client
// Read-only data is data that the server can modify, but the client can only read
var updateUserDataRequest = new UpdateUserDataRequest
{
    PlayFabId = context.CallerEntityProfile.Lineage.MasterPlayerAccountId,
    Data = new Dictionary<string, string>()
    {
        {"MoralisProfileId", response.Id.ToString()},
        {"Address", response.Address.ToString()},
        {"ChainId", response.ChainId.ToString()}
    }
};

PlayFabResult<UpdateUserDataResult> updateUserDateResult = await serverApi.UpdateUserReadOnlyDataAsync(updateUserDataRequest);

if (updateUserDateResult.Error == null)
{
    return new OkObjectResult(updateUserDateResult.Result);
}
else
{
    log.LogInformation($"updateUserDateResult.Error.ErrorMessage: {updateUserDateResult.Error.ErrorMessage.ToString()}");
    return new BadRequestObjectResult(updateUserDateResult.Error.ErrorMessage);
}
  1. Your complete ChallengeVerify function should look similar to this:
[FunctionName("ChallengeVerify")]
public static async Task<dynamic> ChallengeVerify(
    [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req, ILogger log)
{
    // Create the function execution's context through the request
    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
    // Deserialize PlayFab context
    dynamic context = JsonConvert.DeserializeObject(requestBody);
    var args = context.FunctionArgument;

    // Get the message from the request
    dynamic message = null;
    if (args != null && args["message"] != null)
    {
        message = args["message"];
    }

    // Get the signature from the request
    dynamic signature = null;
    if (args != null && args["signature"] != null)
    {
        signature = args["signature"];
    }

    CompleteChallengeResponseDto response = null;

    try
    {
        // Connect with the Moralis Authentication Server
        Moralis.AuthApi.MoralisAuthApiClient.Initialize(AuthenticationApiUrl, ApiKey);
        IAuthClientApi AuthenticationApi = Moralis.AuthApi.MoralisAuthApiClient.AuthenticationApi;

        // Create the authentication message and send it back to the client
        CompleteChallengeRequestDto request = new CompleteChallengeRequestDto()
        {
            Message = message,
            Signature = signature
        };

        response = await AuthenticationApi.AuthEndpoint.CompleteChallenge(request, ChainNetworkType.evm);
    }
    catch (ApiException aexp)
    {
        log.LogInformation($"aexp.Message: {aexp.ToString()}");
        return new BadRequestObjectResult(aexp.Message);
    }
    catch (Exception exp)
    {
        log.LogInformation($"exp.Message: {exp.ToString()}");
        return new BadRequestObjectResult(exp.Message);
    }

    try
    {
        // Get the setting from our Azure config and connect with the PlayFab API
        var settings = new PlayFabApiSettings
        {
            TitleId = Environment.GetEnvironmentVariable("PLAYFAB_TITLE_ID", EnvironmentVariableTarget.Process),
            DeveloperSecretKey = Environment.GetEnvironmentVariable("PLAYFAB_DEV_SECRET_KEY", EnvironmentVariableTarget.Process)
        };

        var serverApi = new PlayFabServerInstanceAPI(settings);

        // Update the user read-only data with the validated data and return the response to the client
        // Read-only data is data that the server can modify, but the client can only read
        var updateUserDataRequest = new UpdateUserDataRequest
        {
            PlayFabId = context.CallerEntityProfile.Lineage.MasterPlayerAccountId,
            Data = new Dictionary<string, string>()
            {
                {"MoralisProfileId", response.Id.ToString()},
                {"Address", response.Address.ToString()},
                {"ChainId", response.ChainId.ToString()}
            }
        };

        PlayFabResult<UpdateUserDataResult> updateUserDateResult = await serverApi.UpdateUserReadOnlyDataAsync(updateUserDataRequest);
        
        if (updateUserDateResult.Error == null)
        {
            return new OkObjectResult(updateUserDateResult.Result);
        }
        else
        {
            log.LogInformation($"updateUserDateResult.Error.ErrorMessage: {updateUserDateResult.Error.ErrorMessage.ToString()}");
            return new BadRequestObjectResult(updateUserDateResult.Error.ErrorMessage);
        }
    }
    catch (Exception exp)
    {
        log.LogInformation($"exp.Message: {exp.ToString()}");
        return new BadRequestObjectResult(exp.Message);
    }
}

Add Methods to Retrieve Wallet Balances, Tokens Owned, and NFTs

One of the greatest advantages of using the Moralis SDK is the ability provided to easily retrieve just about any EVM chain data, including native balance, information about ERC-20 tokens, and NFTs. This section provides sample code just for that.

Web3 API

The Moralis SDK provides the ability to easily query and search EVM data of all kinds. A detailed listing and description of the Moralis Web3 API can be found here.

For this demonstration, we will provide examples of how to create Azure Functions to call three operations:
a. One to look up a user's native token balance using the getNativeBalance operation of the Account endpoint.
b. One to retrieve a list of ERC-20 tokens owned by a user using the getTokenBalances operation of the Account endpoint.
c. One to retrieve the NFTs owned by a user using the getNfts operation of the Account endpoint.

Each of the three functions we will now create are almost duplicates of the ChallengeRequest method we created earlier.

  1. Make a copy of the ChallengeRequest method, rename it GetNativeBalance, and delete everything in the try block.
  2. In the try block, add code to initialize and define a Moralis Web3Api object:
// Connect with the Moralis Authentication Server
Moralis.Web3Api.MoralisWeb3ApiClient.Initialize(Web3ApiUrl, ApiKey);
IWeb3Api web3Api = Moralis.Web3Api.MoralisWeb3ApiClient.Web3Api;

ChainList chain = (ChainList)chainid;
  1. Make two copies of this method, renaming one GetTokenBalances and one GetNfts.

GetNativeBalance

  1. In the try block, add a call to the GetNativeBalance operation of the Account endpoint of Web3Api. This operation returns a NativeBalance object which should be returned to the calling application.
NativeBalance response = await web3Api.Account.GetNativeBalance(address, chain);

return new OkObjectResult(response);

GetTokenBalances

  1. In the try block, add a call to the GetTokenBalances operation of the Account endpoint of Web3Api. This operation returns a List object which should be returned to the calling application.
List<Erc20TokenBalance> response = await web3Api.Account.GetTokenBalances(address, chain);

return new OkObjectResult(response);

GetNfts

  1. Add code to capture the cursor and limit parameters passed into the method:
// Get the cursor from the request
string cursor = null;
if (args != null && args["cursor"] != null)
{
    cursor = args["cursor"].ToString();
}

// Get the limit from the request
int limit = 1;
if (args != null && args["limit"] != null)
{
    limit = args["limit"];
    int.TryParse(args["limit"].ToString(), out limit);
}
  1. In the try block, add a call to the GetNfts operation of the Account endpoint of Web3Api. This operation returns a NftOwnerCollection object which should be returned to the calling application.
NftOwnerCollection response = await web3Api.Account.GetNFTs(address, chain, cursor, null, limit);

return new OkObjectResult(response);

Complete MoralisPlayFab.cs

Your complete MoralisPlayFab.cs file should look similar to:

using System;
using System.IO;
using System.Threading.Tasks;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using PlayFab.ServerModels;
using Moralis.Network;
using Moralis.AuthApi.Models;
using Moralis.AuthApi.Interfaces;
using Moralis.Web3Api.Models;
using Moralis.Web3Api.Interfaces;
using Newtonsoft.Json;

namespace PlayFab.AzureFunctions
{
    public static class MoralisPlayFab
    {
        private static string AuthenticationApiUrl = Environment.GetEnvironmentVariable("MORALIS_AUTHENTICATION_API_URL", EnvironmentVariableTarget.Process);
        private static string Web3ApiUrl = Environment.GetEnvironmentVariable("MORALIS_WEB3_API_URL", EnvironmentVariableTarget.Process);
        private static string ApiKey = Environment.GetEnvironmentVariable("MORALIS_API_KEY", EnvironmentVariableTarget.Process);

        [FunctionName("ChallengeRequest")]
        public static async Task<dynamic> ChallengeRequest(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req, ILogger log)
        {
            // Create the function execution's context through the request
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            // Deserialize PlayFab context
            dynamic context = JsonConvert.DeserializeObject(requestBody);
            var args = context.FunctionArgument;

            // Get the address from the request
            dynamic address = null;
            if (args != null && args["address"] != null)
            {
                address = args["address"];
            }

            // Get the chainid from the request
            dynamic chainid = null;
            if (args != null && args["chainid"] != null)
            {
                chainid = args["chainid"];
            }

            try
            {
                // Connect with the Moralis Authentication Server
                Moralis.AuthApi.MoralisAuthApiClient.Initialize(AuthenticationApiUrl, ApiKey);
                IAuthClientApi AuthenticationApi = Moralis.AuthApi.MoralisAuthApiClient.AuthenticationApi;

                // Create the authentication message and send it back to the client
                // Resources must be RFC 3986 URIs
                // More info here: https://eips.ethereum.org/EIPS/eip-4361#message-field-descriptions
                ChallengeRequestDto request = new ChallengeRequestDto()
                {
                    Address = address,
                    ChainId = (long)chainid,
                    Domain = "moralis.io",
                    ExpirationTime = DateTime.UtcNow.AddMinutes(60),
                    NotBefore = DateTime.UtcNow,
                    Resources = new string[] { "https://docs.moralis.io/" },
                    Timeout = 120,
                    Statement = "Please confirm",
                    Uri = "https://moralis.io/"
                };

                ChallengeResponseDto response = await AuthenticationApi.AuthEndpoint.Challenge(request, ChainNetworkType.evm);
                
                return new OkObjectResult(response.Message);
            }
            catch (ApiException aexp)
            {
                log.LogDebug($"aexp.Message: {aexp.ToString()}");
                return new BadRequestObjectResult(aexp.Message);
            }
            catch (Exception exp)
            {
                log.LogDebug($"exp.Message: {exp.ToString()}");
                return new BadRequestObjectResult(exp.Message);
            }
        }

        [FunctionName("ChallengeVerify")]
        public static async Task<dynamic> ChallengeVerify(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req, ILogger log)
        {
            // Create the function execution's context through the request
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            // Deserialize PlayFab context
            dynamic context = JsonConvert.DeserializeObject(requestBody);
            var args = context.FunctionArgument;

            // Get the message from the request
            dynamic message = null;
            if (args != null && args["message"] != null)
            {
                message = args["message"];
            }

            // Get the signature from the request
            dynamic signature = null;
            if (args != null && args["signature"] != null)
            {
                signature = args["signature"];
            }

            CompleteChallengeResponseDto response = null;

            try
            {
                // Connect with the Moralis Authentication Server
                Moralis.AuthApi.MoralisAuthApiClient.Initialize(AuthenticationApiUrl, ApiKey);
                IAuthClientApi AuthenticationApi = Moralis.AuthApi.MoralisAuthApiClient.AuthenticationApi;

                // Create the authentication message and send it back to the client
                CompleteChallengeRequestDto request = new CompleteChallengeRequestDto()
                {
                    Message = message,
                    Signature = signature
                };

                response = await AuthenticationApi.AuthEndpoint.CompleteChallenge(request, ChainNetworkType.evm);
            }
            catch (ApiException aexp)
            {
                log.LogInformation($"aexp.Message: {aexp.ToString()}");
                return new BadRequestObjectResult(aexp.Message);
            }
            catch (Exception exp)
            {
                log.LogInformation($"exp.Message: {exp.ToString()}");
                return new BadRequestObjectResult(exp.Message);
            }

            try
            {
                // Get the setting from our Azure config and connect with the PlayFab API
                var settings = new PlayFabApiSettings
                {
                    TitleId = Environment.GetEnvironmentVariable("PLAYFAB_TITLE_ID", EnvironmentVariableTarget.Process),
                    DeveloperSecretKey = Environment.GetEnvironmentVariable("PLAYFAB_DEV_SECRET_KEY", EnvironmentVariableTarget.Process)
                };

                var serverApi = new PlayFabServerInstanceAPI(settings);

                // Update the user read-only data with the validated data and return the response to the client
                // Read-only data is data that the server can modify, but the client can only read
                var updateUserDataRequest = new UpdateUserDataRequest
                {
                    PlayFabId = context.CallerEntityProfile.Lineage.MasterPlayerAccountId,
                    Data = new Dictionary<string, string>()
                    {
                        {"MoralisProfileId", response.Id.ToString()},
                        {"Address", response.Address.ToString()},
                        {"ChainId", response.ChainId.ToString()}
                    }
                };

                PlayFabResult<UpdateUserDataResult> updateUserDateResult = await serverApi.UpdateUserReadOnlyDataAsync(updateUserDataRequest);
                
                if (updateUserDateResult.Error == null)
                {
                    return new OkObjectResult(updateUserDateResult.Result);
                }
                else
                {
                    log.LogInformation($"updateUserDateResult.Error.ErrorMessage: {updateUserDateResult.Error.ErrorMessage.ToString()}");
                    return new BadRequestObjectResult(updateUserDateResult.Error.ErrorMessage);
                }
            }
            catch (Exception exp)
            {
                log.LogInformation($"exp.Message: {exp.ToString()}");
                return new BadRequestObjectResult(exp.Message);
            }
        }


        [FunctionName("GetNativeBalance")]
        public static async Task<dynamic> GetNativeBalance(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req, ILogger log)
        {
            // Create the function execution's context through the request
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            // Deserialize PlayFab context
            dynamic context = JsonConvert.DeserializeObject(requestBody);
            var args = context.FunctionArgument;

            // Get the address from the request
            string address = null;
            if (args != null && args["address"] != null)
            {
                address = args["address"].ToString().ToLower();
            }

            // Get the chainid from the request
            int chainid = 1;
            if (args != null && args["chainid"] != null)
            {
                chainid = args["chainid"];
                int.TryParse(args["chainid"].ToString(), out chainid);
            }

            try
            {
                // Connect with the Moralis Authentication Server
                Moralis.Web3Api.MoralisWeb3ApiClient.Initialize(Web3ApiUrl, ApiKey);
                IWeb3Api web3Api = Moralis.Web3Api.MoralisWeb3ApiClient.Web3Api;

                ChainList chain = (ChainList)chainid;

                NativeBalance response = await web3Api.Account.GetNativeBalance(address, chain);

                return new OkObjectResult(response);
            }
            catch (ApiException aexp)
            {
                log.LogDebug($"aexp.Message: {aexp.ToString()}");
                return new BadRequestObjectResult(aexp.Message);
            }
            catch (Exception exp)
            {
                log.LogDebug($"exp.Message: {exp.ToString()}");
                return new BadRequestObjectResult(exp.Message);
            }
        }
        
        [FunctionName("GetNfts")]
        public static async Task<dynamic> GetNfts(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req, ILogger log)
        {
            // Create the function execution's context through the request
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            // Deserialize PlayFab context
            dynamic context = JsonConvert.DeserializeObject(requestBody);
            var args = context.FunctionArgument;

            // Get the address from the request
            string address = null;
            if (args != null && args["address"] != null)
            {
                address = args["address"].ToString().ToLower();
            }

            // Get the chainid from the request
            int chainid = 1;
            if (args != null && args["chainid"] != null)
            {
                chainid = args["chainid"];
                int.TryParse(args["chainid"].ToString(), out chainid);
            }

            // Get the cursor from the request
            string cursor = null;
            if (args != null && args["cursor"] != null)
            {
                cursor = args["cursor"].ToString();
            }

            // Get the limit from the request
            int limit = 1;
            if (args != null && args["limit"] != null)
            {
                limit = args["limit"];
                int.TryParse(args["limit"].ToString(), out limit);
            }

            try
            {
                // Connect with the Moralis Authentication Server
                Moralis.Web3Api.MoralisWeb3ApiClient.Initialize(Web3ApiUrl, ApiKey);
                IWeb3Api web3Api = Moralis.Web3Api.MoralisWeb3ApiClient.Web3Api;

                ChainList chain = (ChainList)chainid;

                NftOwnerCollection response = await web3Api.Account.GetNFTs(address, chain, cursor, null, limit);

                return new OkObjectResult(response);
            }
            catch (ApiException aexp)
            {
                log.LogDebug($"aexp.Message: {aexp.ToString()}");
                return new BadRequestObjectResult(aexp.Message);
            }
            catch (Exception exp)
            {
                log.LogDebug($"exp.Message: {exp.ToString()}");
                return new BadRequestObjectResult(exp.Message);
            }
        }
        
        [FunctionName("GetTokenBalances")]
        public static async Task<dynamic> GetTokenBalances(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req, ILogger log)
        {
            // Create the function execution's context through the request
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            // Deserialize PlayFab context
            dynamic context = JsonConvert.DeserializeObject(requestBody);
            var args = context.FunctionArgument;

            // Get the address from the request
            string address = null;
            if (args != null && args["address"] != null)
            {
                address = args["address"].ToString().ToLower();
            }

            // Get the chainid from the request
            int chainid = 1;
            if (args != null && args["chainid"] != null)
            {
                chainid = args["chainid"];
                int.TryParse(args["chainid"].ToString(), out chainid);
            }

            try
            {
                // Connect with the Moralis Authentication Server
                Moralis.Web3Api.MoralisWeb3ApiClient.Initialize(Web3ApiUrl, ApiKey);
                IWeb3Api web3Api = Moralis.Web3Api.MoralisWeb3ApiClient.Web3Api;

                ChainList chain = (ChainList)chainid;

                List<Erc20TokenBalance> response = await web3Api.Account.GetTokenBalances(address, chain);

                return new OkObjectResult(response);
            }
            catch (ApiException aexp)
            {
                log.LogDebug($"aexp.Message: {aexp.ToString()}");
                return new BadRequestObjectResult(aexp.Message);
            }
            catch (Exception exp)
            {
                log.LogDebug($"exp.Message: {exp.ToString()}");
                return new BadRequestObjectResult(exp.Message);
            }
        }
    }
}

Deploy Azure Functions with Visual Studio Code

This section walks through deploying Azure Functions. 1. Sign in to Azure: 422422
  1. Right-click on MoralisAzureFunctions and select Deploy to Function App:
509509
  1. Select Deploy:
568568
  1. Go to your PlayFab title ID dashboard and go to Automation:
292292
  1. Select Register function and set the Trigger type to HTTP. Enter ChallengeRequest as the function name and copy the function URL from Visual Studio Code. Right-click on ChallengeRequest under Functions and select Copy Function Url, and paste it into the Function URL field. Select Register function.
480480
  1. Create another Register function and set the Trigger type to HTTP. Enter ChallengeVerify as the function name and copy the function URL from Visual Studio Code. Right-click on ChallengeVerify under Functions and select Copy Function Url, and paste it into the Function URL field. Select Register function.
466466
  1. Repeat step #6 above for the GetNativeBalance, GetTokenBalances, and GetNfts operations.

Set Up Unity and Connect Microsoft Azure PlayFab

This section describes how to set up a Unity application and connect to PlayFab and enable secure Web3 authentication using the Moralis SDK.
  1. Download and extract the Moralis PlayFab Unity demo project: https://github.com/MoralisWeb3/demo-unity-moralis-auth-via-playfab/archive/refs/heads/main.zip.
  2. You will notice that there are two folders in this project:
    a. Complete - Contains the completed project. Open this project if you just want to run the application.
    b. Starter - Contains an uncompleted project. Open this project if you want to work through the tutorial.
    Open the desired project in Unity (recommend version: 2020.3.34f1).
  3. Open PlayFab Editor Extensions (If the PlayFab menu doesn’t show this, restart Unity).
12861286
  1. Login with your email and password:
10581058
  1. Go to Settings > Project:
11341134
  1. Select your Studio and Title ID.
  2. Go to Scenes and open SampleScene:
369369

Programmatically Interact with Moralis via PlayFab

If you choose the Starter project, this section will walk you through the code necessary to integrate with Moralis via PlayFab.

If you choose the Complete project, feel free to skip this section and play the game!.

Programmatically integrate with Moralis via PlayFab. 316316
  1. In the Hierarchy view, right-click and select Create Empty. Name this object MoralisWeb3AuthService.
  2. In the Assets/Scripts folder view, create a new script called MoralisWeb3AuthService.cs:
11821182
  1. Drag and drop the MoralisWeb3AuthService.cs script into the MoralisWeb3AuthService object.
  2. Open the edit the MoralisWeb3AuthService.cs script.
  3. Remove the Start and Update functions.
  4. Update the using statements to include required PlayFab, WalletConnect, Unity, and Moralis resources. Like this:
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using PlayFab;
using PlayFab.CloudScriptModels;
using WalletConnectSharp.Unity;
using Assets.Scripts.Moralis;
  1. Next, we want to be able to respond to two events that will be evoked by AuthenticationKit. These will show in our scene object. Add two Unity events, OnSuccess and OnFailed. Also, add a reference to AuthenticationKit. Your MoralisWeb3AuthService.cs should now look similar to this:
  /// <summary>
  /// Invoked when authentication was 
  /// </summary>
  [Header("Events")] public UnityEvent OnSuccess = new UnityEvent();

  /// <summary>
  /// Invoked when State==AuthenticationKitState.Disconnected
  /// </summary>
  public UnityEvent OnFailed = new UnityEvent();

  private AuthenticationKit authenticationKit = null;
  1. Add an Awake method that initializes the authenticationKit variable:
public void Awake()
{
    authenticationKit = FindObjectOfType<AuthenticationKit>(true);
}
  1. Next, we will create a method to call the ChallangeRequest Azure Function via PlayFab. In the MoralisWeb3AuthService class, create a private method called CreateMessage. This method should accept two parameters, address (string) and chainid (int), and return void.
  2. To communicate with PlayFab, we will use the ExecuteFunction method of the PlayFabCloudScriptAPI object provided by PlayFab. This function accepts three parameters, an ExecuteFunctionRequest object, a callback to handle a success response, and a callback to handle an error response. With these, your CreateMessage method should look similar to this:
private void CreateMessage(string address, int chainid)
{
    // Get message from Moralis with PlayFab Azure Functions 
    PlayFabCloudScriptAPI.ExecuteFunction(new ExecuteFunctionRequest()
    {
        Entity = null, // Security information
        FunctionName = null, //This should be the name of your Azure Function that you created.
        FunctionParameter = null, //This is the data that you would want to pass into your function.
        GeneratePlayStreamEvent = true //Set this to true if you would like this call to show up in PlayStream (logging)
    }, async (ExecuteFunctionResult result) =>
    {
    }, (PlayFabError error) =>
    {
    });
}
  1. Set up the security information for the call. To do this, update the Entity property of ExecuteFunctionRequest to:
Entity = new PlayFab.CloudScriptModels.EntityKey()
{
    Id = PlayFabSettings.staticPlayer.EntityId, // Get this from when you logged in,
    Type = PlayFabSettings.staticPlayer.EntityType, // Get this from when you logged in
},
  1. Update the error callback parameter of PlayFabCloudScriptAPI.ExecuteFunction to invoke the OnFailed Unity event:
(PlayFabError error) =>
{
    Debug.Log($"Oops Something went wrong: {error.GenerateErrorReport()}");
    // If there is an error, fire the OnFailed event
    OnFailed.Invoke();
});
  1. Create a copy of the CreateMessage method and name it Authenticate. Change the parameters to message (string) and signature (string).
  2. In the CreateMessage method, find the FunctionName parameter of ExecuteFunctionRequest and set this to "ChallengeRequest". This tells PlayFab which of the functions should be called.
  3. In the CreateMessage method, find the FunctionParameter parameter of the ExecuteFunctionRequest and change it so that it passes in the two parameters supplied to the CreateMessage method:
FunctionParameter =
    new Dictionary<string, object>() //This is the data that you would want to pass into your function.
    {
        { "address", address },
        { "chainid", chainid }
    },
  1. In the success callback parameter, add a conditional statement that checks if a PlayFab limit exceeded condition exists; if it does, invoke OnFailed:
if (result.FunctionResultTooLarge ?? false)
{
    Debug.Log(
        "This can happen if you exceed the limit that can be returned from an Azure Function; see PlayFab Limits Page for details.");
    // If there is an error, fire the OnFailed event
    OnFailed.Invoke();
    return;
}
  1. Still, within success callback, retrieve the result. If a result was returned, extract the message returned by Moralis and request the user to cryptographically sign it.
  2. If the user signs the message, call the Authenticate method, passing in the message and the resulting signature. If the signing of the message failed, invoke OnFailed. The complete CreateMessage method should look similar to this:
private void CreateMessage(string address, int chainid)
{
    // Get message from Moralis with PlayFab Azure Functions 
    PlayFabCloudScriptAPI.ExecuteFunction(new ExecuteFunctionRequest()
    {
        Entity = new PlayFab.CloudScriptModels.EntityKey()
        {
            Id = PlayFabSettings.staticPlayer.EntityId, //Get this from when you logged in,
            Type = PlayFabSettings.staticPlayer.EntityType, //Get this from when you logged in
        },
        FunctionName = "ChallengeRequest", //This should be the name of your Azure Function that you created.
        FunctionParameter =
            new Dictionary<string, object>() //This is the data that you would want to pass into your function.
            {
                { "address", address },
                { "chainid", chainid }
            },
        GeneratePlayStreamEvent = true //Set this to true if you would like this call to show up in PlayStream
    }, async (ExecuteFunctionResult result) =>
    {
        if (result.FunctionResultTooLarge ?? false)
        {
            Debug.Log(
                "This can happen if you exceed the limit that can be returned from an Azure Function; see PlayFab Limits Page for details.");
            // If there is an error, fire the OnFailed event
            OnFailed.Invoke();
            return;
        }

        // Check if we got a message
        string message = result.FunctionResult.ToString();
        if (!String.IsNullOrEmpty(message))
        {
            authenticationKit.State = AuthenticationKitState.WalletSigning;
            
#if !UNITY_WEBGL
            // Sign the message with WalletConnect
            string signature = await WalletConnect.ActiveSession.EthPersonalSign(address, message);
#else
            // Sign the message with Web3
            string signature = await Web3GL.Sign(message);
#endif
            if (!String.IsNullOrEmpty(signature))
            {
                // Send the message and signature to the Authenticate Azure function for validation
                Authenticate(message, signature);
            }
            else
            {
                // If there is no signature fire the OnFailed event
                OnFailed.Invoke();
            }
        }
        else
        {
            // If the is no message fire the OnFailed event
            OnFailed.Invoke();
        }
    }, (PlayFabError error) =>
    {
        Debug.Log($"Oops Something went wrong: {error.GenerateErrorReport()}");
        // If there is an error, fire the OnFailed event
        OnFailed.Invoke();
    });
}
  1. In the Authenticate method, find the FunctionName parameter of the ExecuteFunctionRequest and set this to "ChallengeVerify". This tells PlayFab which of the functions should be called.
  2. In the CreateMessage method, find the FunctionParameter parameter of the ExecuteFunctionRequest and change it so that it passes in the two parameters supplied to the Authenticate method:
FunctionParameter =
    new Dictionary<string, object>() //This is the data that you would want to pass into your function.
    {
        { "message", message},
        { "signature", signature}
    },
  1. In the success callback parameter, add a conditional statement that checks if a PlayFab limit exceeded condition exists; if it does, invoke OnFailed:
if (result.FunctionResultTooLarge ?? false)
{
    Debug.Log(
        "This can happen if you exceed the limit that can be returned from an Azure Function; see PlayFab Limits Page for details.");
    // If there is an error, fire the OnFailed event
    OnFailed.Invoke();
    return;
}
  1. Now, check to see if the result is empty. If not, the verification call succeeded, invoke OnSuccess; otherwise, invoke OnFailed. Your complete Authenticate method should look similar to this:
private void Authenticate(string message, string signature)
{
    // Send the message and signature to the Authenticate Azure function for validation
    PlayFabCloudScriptAPI.ExecuteFunction(new ExecuteFunctionRequest()
    {
        Entity = new PlayFab.CloudScriptModels.EntityKey()
        {
            Id = PlayFabSettings.staticPlayer.EntityId, //Get this from when you logged in,
            Type = PlayFabSettings.staticPlayer.EntityType, //Get this from when you logged in
        },
        FunctionName = "ChallengeVerify", //This should be the name of your Azure Function that you created.
        FunctionParameter =
            new Dictionary<string, object>() //This is the data that you would want to pass into your function.
            {
                { "message", message },
                { "signature", signature }
            },
        GeneratePlayStreamEvent = true //Set this to true if you would like this call to show up in PlayStream
    }, (ExecuteFunctionResult result) =>
    {
        if (result.FunctionResultTooLarge ?? false)
        {
            Debug.Log(
                "This can happen if you exceed the limit that can be returned from an Azure Function; see PlayFab Limits Page for details.");
            // If there is an error, fire the OnFailed event
            OnFailed.Invoke();
            return;
        }

        // If the authentication succeeded, the user profile is updated and we get the UpdateUserDataAsync return values as response
        // If it failed it returns empty
        if (!String.IsNullOrEmpty(result.FunctionResult.ToString()))
        {
            authenticationKit.State = AuthenticationKitState.WalletSigned;
            
            // On success fire the OnSuccess event
            OnSuccess.Invoke();
        }
        else
        {
            // If the response is empty, fire the OnFailed event
            OnFailed.Invoke();
        }
    }, (PlayFabError error) =>
    {
        Debug.Log($"Oops Something went wrong: {error.GenerateErrorReport()}");
        // If there is an error, fire the OnFailed event
        OnFailed.Invoke();
    });
}
  1. The final step is to add a method that will be called when the authentication kit changes state. When the state changes to AuthenticationKitState.WalletConnected, we can extract the user's wallet address and chain ID to call the CreateMessage method. The code for this method is:
public void StateObservable_OnValueChanged(AuthenticationKitState authenticationKitState)
{
    switch (authenticationKitState)
    {
        case AuthenticationKitState.WalletConnected:

#if !UNITY_WEBGL
            // Get the address and chain ID with WalletConnect 
            string address = WalletConnect.ActiveSession.Accounts[0];
            int chainid = WalletConnect.ActiveSession.ChainId;
#else
            // Get the address and chain ID with Web3 
            string address = Web3GL.Account().ToLower();
            int chainid = Web3GL.ChainId();
#endif
            // Create sign message 
            CreateMessage(address, chainid);
            break;
    }
}

Your complete MoralisWeb3AuthService.cs file should now look similar to this:

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using PlayFab;
using PlayFab.CloudScriptModels;
using WalletConnectSharp.Unity;
using Assets.Scripts.Moralis;

public class MoralisWeb3AuthService : MonoBehaviour
{
    //  Events ----------------------------------------

    /// <summary>
    /// Invoked when authentication was 
    /// </summary>
    [Header("Events")] public UnityEvent OnSuccess = new UnityEvent();

    /// <summary>
    /// Invoked when State==AuthenticationKitState.Disconnected
    /// </summary>
    public UnityEvent OnFailed = new UnityEvent();

    private AuthenticationKit authenticationKit = null;

    public void Awake()
    {
        authenticationKit = FindObjectOfType<AuthenticationKit>(true);
    }

    public void StateObservable_OnValueChanged(AuthenticationKitState authenticationKitState)
    {
        switch (authenticationKitState)
        {
            case AuthenticationKitState.WalletConnected:

#if !UNITY_WEBGL
                // Get the address and chain ID with WalletConnect 
                string address = WalletConnect.ActiveSession.Accounts[0];
                int chainid = WalletConnect.ActiveSession.ChainId;
#else
                // Get the address and chain ID with Web3 
                string address = Web3GL.Account().ToLower();
                int chainid = Web3GL.ChainId();
#endif
                // Create sign message 
                CreateMessage(address, chainid);
                break;
        }
    }

    private void CreateMessage(string address, int chainid)
    {
        // Get message from Moralis with PlayFab Azure Functions 
        PlayFabCloudScriptAPI.ExecuteFunction(new ExecuteFunctionRequest()
        {
            Entity = new PlayFab.CloudScriptModels.EntityKey()
            {
                Id = PlayFabSettings.staticPlayer.EntityId, //Get this from when you logged in,
                Type = PlayFabSettings.staticPlayer.EntityType, //Get this from when you logged in
            },
            FunctionName = "ChallengeRequest", //This should be the name of your Azure Function that you created.
            FunctionParameter =
                new Dictionary<string, object>() //This is the data that you would want to pass into your function.
                {
                    { "address", address },
                    { "chainid", chainid }
                },
            GeneratePlayStreamEvent = true //Set this to true if you would like this call to show up in PlayStream
        }, async (ExecuteFunctionResult result) =>
        {
            if (result.FunctionResultTooLarge ?? false)
            {
                Debug.Log(
                    "This can happen if you exceed the limit that can be returned from an Azure Function; see PlayFab Limits Page for details.");
                // If there is an error, fire the OnFailed event
                OnFailed.Invoke();
                return;
            }

            // Check if we got a message
            string message = result.FunctionResult.ToString();
            if (!String.IsNullOrEmpty(message))
            {
                authenticationKit.State = AuthenticationKitState.WalletSigning;
                
#if !UNITY_WEBGL
                // Sign the message with WalletConnect
                string signature = await WalletConnect.ActiveSession.EthPersonalSign(address, message);
#else
                // Sign the message with Web3
                string signature = await Web3GL.Sign(message);
#endif
                if (!String.IsNullOrEmpty(signature))
                {
                    // Send the message and signature to the Authenticate Azure function for validation
                    Authenticate(message, signature);
                }
                else
                {
                    // If there is no signature, fire the OnFailed event
                    OnFailed.Invoke();
                }
            }
            else
            {
                // If there is no message, fire the OnFailed event
                OnFailed.Invoke();
            }
        }, (PlayFabError error) =>
        {
            Debug.Log($"Oops Something went wrong: {error.GenerateErrorReport()}");
            // If there is an error, fire the OnFailed event
            OnFailed.Invoke();
        });
    }

    private void Authenticate(string message, string signature)
    {
        // Send the message and signature to the Authenticate Azure function for validation
        PlayFabCloudScriptAPI.ExecuteFunction(new ExecuteFunctionRequest()
        {
            Entity = new PlayFab.CloudScriptModels.EntityKey()
            {
                Id = PlayFabSettings.staticPlayer.EntityId, //Get this from when you logged in,
                Type = PlayFabSettings.staticPlayer.EntityType, //Get this from when you logged in
            },
            FunctionName = "ChallengeVerify", //This should be the name of your Azure Function that you created.
            FunctionParameter =
                new Dictionary<string, object>() //This is the data that you would want to pass into your function.
                {
                    { "message", message },
                    { "signature", signature }
                },
            GeneratePlayStreamEvent = true //Set this to true if you would like this call to show up in PlayStream
        }, (ExecuteFunctionResult result) =>
        {
            if (result.FunctionResultTooLarge ?? false)
            {
                Debug.Log(
                    "This can happen if you exceed the limit that can be returned from an Azure Function; see PlayFab Limits Page for details.");
                // If there is an error, fire the OnFailed event
                OnFailed.Invoke();
                return;
            }

            // If the authentication succeeded, the user profile is updated and we get the UpdateUserDataAsync return values as response
            // If it fails it returns empty
            if (!String.IsNullOrEmpty(result.FunctionResult.ToString()))
            {
                authenticationKit.State = AuthenticationKitState.WalletSigned;
                
                // On success, fire the OnSuccess event
                OnSuccess.Invoke();
            }
            else
            {
                // If the response is empty, fire the OnFailed event
                OnFailed.Invoke();
            }
        }, (PlayFabError error) =>
        {
            Debug.Log($"Oops Something went wrong: {error.GenerateErrorReport()}");
            // If the is a error fire the OnFailed event
            OnFailed.Invoke();
        });
    }
}

Steps to Wire Up MoralisWeb3AuthService

  1. In the Unity UI, in the Hierarchy view, select the MoralisWeb3AuthService object and add entries under both On Success and On Failed. Drag AuthenticationKit into each of these, selecting AuthenticationKit/Connect for On Success and AuthenticationKit/Disconnect for On Failed:
873873
  1. In the Unity UI, in the Hierarchy view, select the AuthenticationKit object. Add an entry to On State Changed (AuthenticationStateChanged). Drag the MoralisWeb3AuthService object to this entry and select MoralisWeb3AuthService/StateObservable_OnValueChanged:
10621062

Play the Game!

  1. Run the SampleScene and select PLAY AS GUEST:
14131413
  1. Select CONNECT WALLET:
14071407
  1. Select CONNECT and scan the QR code with your wallet and follow all the steps.
  2. If you return to the LOGGED IN screen, the wallet information is added to the player's profile under Players > Player Data:
16101610
  1. Celebrate your epic setup and start thinking of all the Web3 games you can now build!

Did this page help you?