> For the complete documentation index, see [llms.txt](https://docs.metaproprotocol.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.metaproprotocol.com/metapro-protocol/unity-plugin/reference/afp-additional-features-packages/safetransferfrom.md).

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

```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.metaproprotocol.com/metapro-protocol/unity-plugin/reference/afp-additional-features-packages/safetransferfrom.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
