Phoenix

This AFP contains a call for the Phoenix functions from smart contract. Most important code is in Phoenix, PhoenixOutputParser, PhoenixDecoder and PhoenixQuest classes.

This package also offers scripts that deal with showing player the UI screen (PhoenixQuestPopup).

All scripts are in namespace metaproSDK.Scripts.AFP.Phoenix.

In Phoenix.cs you will find all data about connection to smart contract - contract address, ABI, token address. There’re also connections to dev and prod systems of metapromarket.

By calling function GetQuestData AFP make request to smart contract to read from onchain data by method “getQuestById”. In Phoenix we have to decode that data so PhoenixDecoder saves data into an object and PhoenixOutputParser parse data to proper type.

Phoenix.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using metaproSDK.Scripts.AFP.Phoenix.Serialization;
using metaproSDK.Scripts.AFP.Phoenix.UI;
using metaproSDK.Scripts.AFP.SafeTransferFrom;
using metaproSDK.Scripts.AFP.SafeTransferFrom.Serialization;
using metaproSDK.Scripts.Serialization;
using Nethereum.Web3;
using Newtonsoft.Json;
using Serialization;
using UnityEditor;
using UnityEngine;
using UnityEngine.Networking;
using WalletConnectSharp.Core.Models.Ethereum;
using WalletConnectSharp.Unity;

namespace metaproSDK.Scripts.AFP.Phoenix
{
    public class Phoenix : MonoBehaviour
    {
        [SerializeField] private PhoenixQuestPopup questPopup;

        private string _tokenContractAddressTest = "0x91945DCD15b44eB33dE4D00897843C1Ee524fA88"; //testnet

        private string _phoenixAddressTest = "0xd3a4912f174b7904b1Dc00d39842E5CFB22B6112";
        
        // we don’t include here whole ABI
        private string _phoenixABITest =
            @"[{""inputs"":[], … ""outputs"":[],""stateMutability"":""nonpayable"",""type"":""function""}]";

        private const string _functionSignature = "setApprovalForAll(address,bool)";
        private const string _completeQuestSignature = "completeQuest(uint256,bytes)";
        
        private PhoenixQuest _selectedQuest;

        private List<PhoenixQuestToken> _inputTokens = new();
        private List<PhoenixQuestToken> _outputTokens = new();

        private string TEST_SERVER_URL = "https://test-api.metaproprotocol.com";
        private string PROD_SERVER_URL = "https://api.metaproprotocol.com";
        public string ServerRequestUrl => TEST_SERVER_URL; // PROD_SERVER_URL;
        public PhoenixQuest SelectedQuest => _selectedQuest;
        
        public List<PhoenixQuestToken> InputTokens => _inputTokens;
        public List<PhoenixQuestToken> OutputTokens => _outputTokens;
        //example of BSC API Key
        private string bscApiKey = "N3DZJ2U62UJER17AXJZKD24FS8VS68F93U";
        
        public void OpenQuestPopup()
        {
            StartCoroutine(DownloadTokenData(_selectedQuest.InputTokenIds, true));
            StartCoroutine(DownloadTokenData(_selectedQuest.OutputTokenIds, false));
            questPopup.OpenInitialScreen();
        }
        
        private void Start()
        {
            GetQuestData(3);
        }

        private async void GetQuestData(int questId)
        {
            var web3Test = new Web3("https://data-seed-prebsc-1-s1.binance.org:8545");
            var contract = web3Test.Eth.GetContract(_phoenixABITest, _phoenixAddressTest);
            var function = contract.GetFunction("getQuestById");
            var result = await function.CallDecodingToDefaultAsync(questId);

            var phoenixQuest = PhoenixDecoder.DecodePhoenixQuest(result);
            Debug.Log(phoenixQuest);
            _selectedQuest = phoenixQuest;
            OpenQuestPopup();
        }

        

        public void AllowTokenControlBySC()
        {
            questPopup.ShowTransactionProcessingScreen();
            var transactionHash = "";
            transactionHash += TransactionEncoder.EncodeFunction(_functionSignature);
            var fromWalletAddress = new WalletAddress();
            fromWalletAddress.value = _phoenixAddressTest;
            transactionHash += TransactionEncoder.EncodeParam(fromWalletAddress);
            transactionHash += TransactionEncoder.EncodeParam(true);
            StartCoroutine(TransactionRequest(transactionHash));
        }

        
         private IEnumerator TransactionRequest(string transactionHashData)
        {
            TransactionData data = new TransactionData();

            data.from = WalletConnect.ActiveSession.Accounts[0];
            data.to = _tokenContractAddressTest;
            data.data = transactionHashData;

            var task = Task.Run(async () => await WalletConnect.ActiveSession.EthSendTransaction(data));
            
            yield return new WaitUntil(() => task.IsCompleted);
            if (task.IsFaulted)
            {
                foreach (var innerException in task.Exception.InnerExceptions)
                {
                    Debug.LogWarning(innerException.Message);
                }
                questPopup.HideTransactionProcessingScreen();
                yield break;
            }
            Debug.Log(task.Result);

            if (bscApiKey == "")
            {
                Debug.LogWarning("BSC api key not found");
                questPopup.HideTransactionProcessingScreen();
                yield break;
            }
            
            var txHash = task.Result;

            var bscApi = PluginManager.Instance.IsTestnetSelected ? "https://api-testnet.bscscan.com" : "https://api.bscscan.com";
            
            var requestUrl = $"{bscApi}/api?module=transaction" +
                             $"&action=getstatus" +
                             $"&txhash={txHash}" +
                             $"&apikey={bscApiKey}";
            var request = UnityWebRequest.Get(requestUrl);
            yield return request.SendWebRequest();
            
            Debug.Log($"Transaction hash: {request.result}");
            var apiResponse = JsonConvert.DeserializeObject<BSCApiRespone>(request.downloadHandler.text);

            if (apiResponse != null && apiResponse.result.isError == "1")
            {
                Debug.LogWarning("Transaction occured an error");
                Debug.LogWarning(apiResponse.result.errDescription);
            }
            
            questPopup.EnableSecondStage();
            questPopup.HideTransactionProcessingScreen();
        }
         
         
         public void CompleteQuest()
         {
             questPopup.ShowTransactionProcessingScreen();
             var transactionHash = "";
             transactionHash += TransactionEncoder.EncodeFunction(_completeQuestSignature);
             transactionHash += TransactionEncoder.EncodeParam(_selectedQuest.QuestId);
             transactionHash += TransactionEncoder.EncodeLastCompleteQuestBytes();
             StartCoroutine(CompleteQuestTransactionRequest(transactionHash));
         }
         
         
         private IEnumerator CompleteQuestTransactionRequest(string transactionHashData)
         {
             TransactionData data = new TransactionData();

             data.from = WalletConnect.ActiveSession.Accounts[0];
             data.to = _phoenixAddressTest;
             data.data = transactionHashData;

             var task = Task.Run(async () => await WalletConnect.ActiveSession.EthSendTransaction(data));
            
             yield return new WaitUntil(() => task.IsCompleted);
             if (task.IsFaulted)
             {
                 foreach (var innerException in task.Exception.InnerExceptions)
                 {
                     Debug.LogWarning(innerException.Message);
                 }
                 questPopup.HideTransactionProcessingScreen();
                 yield break;
             }
             Debug.Log(task.Result);

             if (bscApiKey == "")
             {
                 Debug.LogWarning("BSC api key not found");
                 questPopup.HideTransactionProcessingScreen();
                 yield break;
             }
            
             var txHash = task.Result;

             var bscApi = PluginManager.Instance.IsTestnetSelected ? "https://api-testnet.bscscan.com" : "https://api.bscscan.com";
            
             var requestUrl = $"{bscApi}/api?module=transaction" +
                              $"&action=getstatus" +
                              $"&txhash={txHash}" +
                              $"&apikey={bscApiKey}";
             var request = UnityWebRequest.Get(requestUrl);
             yield return request.SendWebRequest();
            
             Debug.Log($"Transaction hash: {request.result}");
             var apiResponse = JsonConvert.DeserializeObject<BSCApiRespone>(request.downloadHandler.text);

             if (apiResponse != null && apiResponse.result.isError == "1")
             {
                 Debug.LogWarning("Transaction occured an error");
                 Debug.LogWarning(apiResponse.result.errDescription);
             }
             questPopup.HideTransactionProcessingScreen();
             questPopup.ShowScreen(PhoenixScreenType.Success);
         }
        
        private IEnumerator DownloadTokenData(int[] tokenIds, bool inputTokens)
        {
            var nftParams = String.Join('&', tokenIds.Select(p => "tokenIds=" + p).ToList());
            var requestUrl = ServerRequestUrl + "/ms/nft/v1/tokens?" + nftParams;

            UnityWebRequest getNfts = UnityWebRequest.Get(requestUrl);

            yield return getNfts.SendWebRequest();

            if (getNfts.result == UnityWebRequest.Result.ConnectionError)
            {
                Debug.LogError(getNfts.error);
                yield break;
            }

            Debug.Log(getNfts.downloadHandler.text);

            var appNftResult = JsonConvert.DeserializeObject<Results<NftUserTokenResultTest>>(getNfts.downloadHandler.text);

            foreach (var nftTokensResult in appNftResult.results)
            {
                var phoenixQuestToken = new PhoenixQuestToken();
                phoenixQuestToken.imageUrl = nftTokensResult.token.image;
                phoenixQuestToken.tokenId = nftTokensResult.token._tokenId;
                if (inputTokens)
                {
                    phoenixQuestToken.amount = _selectedQuest.InputTokenQuantities[Array.IndexOf(tokenIds, phoenixQuestToken.tokenId)];
                    _inputTokens.Add(phoenixQuestToken);
                }
                else
                {
                    phoenixQuestToken.amount = _selectedQuest.OutputTokenQuantity[Array.IndexOf(tokenIds, phoenixQuestToken.tokenId)];
                    phoenixQuestToken.available = true;
                    _outputTokens.Add(phoenixQuestToken);
                }
            }

            foreach (var questToken in _inputTokens)
            {
                StartCoroutine(GetTokenTexture(questToken));
            }
            foreach (var questToken in _outputTokens)
            {
                StartCoroutine(GetTokenTexture(questToken));
            }

        }

        private IEnumerator GetTokenTexture(PhoenixQuestToken phoenixQuestToken)
        {
            UnityWebRequest www = UnityWebRequestTexture.GetTexture(phoenixQuestToken.imageUrl);
            yield return www.SendWebRequest();
            if (www.result != UnityWebRequest.Result.Success)
            {
                Debug.Log(www.error);
                Debug.Log(www.downloadHandler.error);
                StartCoroutine(GetGifData(phoenixQuestToken));
            }
            else
            {
                Debug.Log("Downloaded Texture");
                Texture texture = ((DownloadHandlerTexture)www.downloadHandler).texture;
                phoenixQuestToken.texture = texture;
            }
        }

        private IEnumerator GetGifData(PhoenixQuestToken phoenixQuestToken)
        {
            UnityWebRequest www = UnityWebRequest.Get(phoenixQuestToken.imageUrl);
            yield return www.SendWebRequest();
            if (www.result != UnityWebRequest.Result.Success)
            {
                Debug.Log(www.error);
                Debug.Log(www.downloadHandler.error);
            }
            else
            {
#if UNITY_EDITOR
                Debug.Log("Downloaded GIF texture");
                var relativePath = "Assets/downloadedGif" + phoenixQuestToken.tokenId + ".gif";
                ByteArrayToFile(relativePath, www.downloadHandler.data);
                AssetDatabase.ImportAsset(relativePath);
                AssetDatabase.Refresh();
                TextureImporter importer = (TextureImporter)TextureImporter.GetAtPath(relativePath);

                importer.isReadable = true;
                importer.textureType = TextureImporterType.Sprite;

                TextureImporterSettings importerSettings = new TextureImporterSettings();
                importer.ReadTextureSettings(importerSettings);
                importerSettings.textureType = TextureImporterType.Sprite;
                importerSettings.spriteExtrude = 0;
                importerSettings.spriteGenerateFallbackPhysicsShape = false;
                importerSettings.spriteMeshType = SpriteMeshType.FullRect;
                importerSettings.spriteMode = (int)SpriteImportMode.Single;
                importer.SetTextureSettings(importerSettings);
                importer.spriteImportMode = SpriteImportMode.Single;
                importer.maxTextureSize = 1024; // or whatever
                importer.alphaIsTransparency = true;
                importer.textureCompression = TextureImporterCompression.Uncompressed;
                importer.alphaSource = TextureImporterAlphaSource.FromInput;
                EditorUtility.SetDirty(importer);

                importer.SaveAndReimport();
                Sprite spriteImage = (Sprite)AssetDatabase.LoadAssetAtPath(relativePath, typeof(Sprite));
                phoenixQuestToken.texture = spriteImage.texture;
#endif
            }
        }

        public bool ByteArrayToFile(string fileName, byte[] byteArray)
        {
            try
            {
                using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write))
                {
                    fs.Write(byteArray, 0, byteArray.Length);
                    return true;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception caught in process: {0}", ex);
                return false;
            }
        }
    }
}

ParameterOutputParser.cs

using Nethereum.ABI.FunctionEncoding;
using UnityEngine;

namespace metaproSDK.Scripts.AFP.Phoenix
{
    public static class ParameterOutputParser<T>
    {
        public static T ParseParameter(ParameterOutput parameterOutput)
        {
            if (parameterOutput.Parameter.DecodedType == typeof(T))
            {
                return (T)parameterOutput.Result;
            }

            Debug.LogError($"Parsing error: {parameterOutput.Parameter.DecodedType} can't be parsed to {typeof(T)}. Value: {parameterOutput.Result}");
            return default;
        }
        
    }
}

PhoenixDecoder.cs

using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using metaproSDK.Scripts.AFP.Phoenix.Serialization;
using Nethereum.ABI.FunctionEncoding;

namespace metaproSDK.Scripts.AFP.Phoenix
{
    public static class PhoenixDecoder
    {
        public static PhoenixQuest DecodePhoenixQuest(List<ParameterOutput> parameterOutputs)
        {
            var phoenixQuest = new PhoenixQuest();

            phoenixQuest.QuestId = (int)ParameterOutputParser<BigInteger>.ParseParameter(parameterOutputs[0]);
            phoenixQuest.TokenContractAddress = ParameterOutputParser<string>.ParseParameter(parameterOutputs[1]);
            phoenixQuest.TotalNumberOfQuests = (int)ParameterOutputParser<BigInteger>.ParseParameter(parameterOutputs[2]);
            phoenixQuest.NumberOfQuestsCompleted = (int)ParameterOutputParser<BigInteger>.ParseParameter(parameterOutputs[3]);
            phoenixQuest.InputTokenIds = ParameterOutputParser<List<BigInteger>>.ParseParameter(parameterOutputs[4]).Select(p => (int)p).ToArray();
            phoenixQuest.OutputTokenIds = ParameterOutputParser<List<BigInteger>>.ParseParameter(parameterOutputs[5]).Select(p => (int)p).ToArray();
            phoenixQuest.InputTokenQuantities = ParameterOutputParser<List<BigInteger>>.ParseParameter(parameterOutputs[6]).Select(p => (int)p).ToArray();
            phoenixQuest.OutputTokenQuantity = ParameterOutputParser<List<BigInteger>>.ParseParameter(parameterOutputs[7]).Select(p => (int)p).ToArray();
            phoenixQuest.OperatorAddress = ParameterOutputParser<string>.ParseParameter(parameterOutputs[8]);
            phoenixQuest.MultipleParticipation = ParameterOutputParser<bool>.ParseParameter(parameterOutputs[9]);
            phoenixQuest.StartBlock = ParameterOutputParser<BigInteger>.ParseParameter(parameterOutputs[10]);
            phoenixQuest.EndBlock = ParameterOutputParser<BigInteger>.ParseParameter(parameterOutputs[11]);
            phoenixQuest.Valid = ParameterOutputParser<bool>.ParseParameter(parameterOutputs[12]);
            
            return phoenixQuest;
        }
    }
}

PhoenixQuest.cs

using System.Numerics;

namespace metaproSDK.Scripts.AFP.Phoenix.Serialization
{
    public class PhoenixQuest
    {
        public int QuestId;
        public string TokenContractAddress;
        public int TotalNumberOfQuests;
        public int NumberOfQuestsCompleted;
        public int[] InputTokenIds;
        public int[] OutputTokenIds;
        public int[] InputTokenQuantities;
        public int[] OutputTokenQuantity;
        public string OperatorAddress;
        public bool MultipleParticipation;
        public BigInteger StartBlock;
        public BigInteger EndBlock;
        public bool Valid;

        public override string ToString()
        {
            return $"Phoenix quest: {QuestId}, {TokenContractAddress}, {Valid}, {StartBlock} - {EndBlock}";
        }
    }
}

Last updated