SafeTransferFrom

This AFP contains a call for the safeTransferFrom function from smart contract. Most important code is in SafeTransferFrom class and TransactionEncoder class.

This package also offers scripts that deal with showing player the UI screen. All scripts are in namespace metaproSDK.Scripts.AFP.SafeTransferFrom.

Interaction with smart contract is achieved by first getting the _smartContractAddress and _functionSignature. Then using ABI specification hash of function signature is combined with encoded function parameters and sended as data in WalletConenct transaction. From parameter of the transaction is the current logged wallet address and To parameter is the smart contract address.

SafeTrasnferFrom.cs

using System;
using System.Collections;
using System.Linq;
using System.Threading.Tasks;
using metaproSDK.Scripts.AFP.SafeTransferFrom.Serialization;
using metaproSDK.Scripts.Serialization;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Networking;
using WalletConnectSharp.Core.Models.Ethereum;
using WalletConnectSharp.Unity;

namespace metaproSDK.Scripts.AFP.SafeTransferFrom
{
    public class SafeTransferFrom : MonoBehaviour
    {
        [SerializeField] private string bscApiKey;
        [SerializeField] private TransferPopupView transferPopupView;
        [SerializeField] private GameObject signTransactionView;
        [SerializeField] private ErrorPopupView transactionFailedView;
        [SerializeField] private TransferSuccessPopupView transactionSucceedView;
        
        private bool _isOpened;
        
        private const string _smartContractAddress = "0xa293D68684Be29540838Dc8A0222De0c43c6b5B4";
        private const string _functionSignature = "safeTransferFrom(address,address,uint256,uint256,bytes)";

        private SafeTransferFromState _currentState;
        private NftTokenData _nftTokenData;

        private void Start()
        {
            _currentState = SafeTransferFromState.Initial;
        }

        public void SetupTransfer(NftTokenData nftTokenData)
        {
            transferPopupView.Setup(nftTokenData);
            _nftTokenData = nftTokenData;
        }

        public void OpenView()
        {
            HideAllViews();
            switch (_currentState)
            {
                case SafeTransferFromState.Initial:
                    transferPopupView.gameObject.SetActive(true);
                    break;
                case SafeTransferFromState.Transaction_Sent:
                    signTransactionView.gameObject.SetActive(true);
                    break;
                case SafeTransferFromState.Transaction_Failed:
                    transactionFailedView.gameObject.SetActive(true);
                    break;
                case SafeTransferFromState.Transaction_Success:
                    transactionSucceedView.gameObject.SetActive(true);
                    break;
                default:
                    throw new ArgumentOutOfRangeException(nameof(_currentState), _currentState, null);
            }

            _isOpened = true;
        }
        
        private void HideAllViews()
        {
            transferPopupView.gameObject.SetActive(false);
            signTransactionView.gameObject.SetActive(false);
            transactionFailedView.gameObject.SetActive(false);
            transactionSucceedView.gameObject.SetActive(false);
        }

        public void CloseView()
        {
            HideAllViews();
            _isOpened = false;
        }

        public void OpenClosePopup()
        {
            if (_isOpened)
            {
                CloseView();
            }
            else
            {
                OpenView();
            }
        }

        public void SetState(SafeTransferFromState newState)
        {
            _currentState = newState;
        }

        public void OpenInitialState()
        {
            _currentState = SafeTransferFromState.Initial;
            OpenView();
        }

        public void SendSelectedToken()
        {
            if (transferPopupView.CheckErrors())
            {
                return;
            }
            
            var fromAddress = WalletConnect.ActiveSession.Accounts[0];
            var toAddress = transferPopupView.ToAddress;
            var tokenId = transferPopupView.TokenId;
            var amount = transferPopupView.Amount;
            SendToken(fromAddress, toAddress, tokenId, amount);
        }

        public void SendToken(string fromAddress, string toAddress, int tokenId, int amount)
        {
            var transactionHash = "";
            transactionHash += TransactionEncoder.EncodeFunction(_functionSignature);
            var fromWalletAddress = new WalletAddress();
            fromWalletAddress.value = fromAddress;
            transactionHash += TransactionEncoder.EncodeParam(fromWalletAddress);
            var toWalletAddress = new WalletAddress();
            toWalletAddress.value = toAddress;
            transactionHash += TransactionEncoder.EncodeParam(toWalletAddress);
            transactionHash += TransactionEncoder.EncodeParam(tokenId);
            transactionHash += TransactionEncoder.EncodeParam(amount);
            transactionHash += TransactionEncoder.EncodeLastBytes();
            StartCoroutine(TransactionRequest(transactionHash));
        }
        
        private IEnumerator TransactionRequest(string transactionHashData)
        {
            SetState(SafeTransferFromState.Transaction_Sent);
            OpenView();
            TransactionData data = new TransactionData();

            data.from = WalletConnect.ActiveSession.Accounts[0];
            data.to = _smartContractAddress;
            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);
                }
                SetState(SafeTransferFromState.Transaction_Failed);
                transactionFailedView.SetupView(task.Exception.InnerExceptions[0].Message);
                OpenView();
                yield break;
            }
            Debug.Log(task.Result);

            if (bscApiKey == "")
            {
                Debug.LogWarning("BSC api key not found");
                yield break;
            }
            
            var txHash = task.Result;
            var requestUrl = $"https://api.bscscan.com/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");
                SetState(SafeTransferFromState.Transaction_Failed);
                transactionFailedView.SetupView(apiResponse.result.errDescription);
                OpenView();
                yield break;
            }
            
            SetState(SafeTransferFromState.Transaction_Success);
            transactionSucceedView.Setup(_nftTokenData, transferPopupView.ToAddress, transferPopupView.Amount, "https://bscscan.com/tx/" + txHash);
            OpenView();
        }

        public void CloseSafeTransfer()
        {
            Destroy(gameObject);
        }
    }
}

TransactionEncoder.cs

using System.Text;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Utilities.Encoders;

namespace metaproSDK.Scripts.AFP.SafeTransferFrom
{
    public class TransactionEncoder
    {
        public static string EncodeFunction(string functionWithParams)
        {
            byte[] inputBytes = Encoding.UTF8.GetBytes(functionWithParams);
            KeccakDigest keccakDigest = new KeccakDigest(256);
            byte[] outputBytes = new byte[keccakDigest.GetDigestSize()];
            keccakDigest.BlockUpdate(inputBytes, 0, inputBytes.Length);
            keccakDigest.DoFinal(outputBytes, 0);
            return Hex.ToHexString(outputBytes)[..8]; //return first 4 bytes of hash 
        }

        private static string SimpleStringEncoding(string input)
        {
            string result = "";

            for (int i = 0; i < 64 - input.Length; i++)
            {
                result += "0";
            }
            result += input;
            return result.ToLower();
        }
        
        public static string EncodeParam(WalletAddress walletAddress)
        {
            return SimpleStringEncoding(walletAddress.value.Substring(2));
        }
        
        public static string EncodeParam(int integer)
        {
            string hexValue = integer.ToString("X");
            return SimpleStringEncoding(hexValue);
        }

        public static string EncodeLastBytes()
        {
            return "00000000000000000000000000000000000000000000000000000000000000a0" +
                   "0000000000000000000000000000000000000000000000000000000000000001" +
                   "0000000000000000000000000000000000000000000000000000000000000000";
        }
    }
}

Last updated