# SafeTransferFrom

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

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";
        }
    }
}

```
