En el artículo anterior vimos como configurar nuestro proyecto para trabajar con Nethereum, y así poder saber el saldo disponible de nuestra billetera. Ahora veremos cómo poder transferir tokens de una billetera a otra, y como comprar o intercambiar tokens directamente en PancakeSwap de forma automática desde nuestro programa.

Primero que todo debemos saber cómo funciona PancakeSwap, y en general, tenemos que saber cómo interactuar con un contrato, que es algo más simple de lo que parece.

Cuando realizamos un intercambio en PancakeSwap, estamos haciendo un llamado a una función en su contrato, en la cual enviamos una serie de variables, estas variables son bastante simples, como por ejemplo definir el monto mínimo que esperamos recibir por el intercambio (llamado slippage o “tolerancia de riesgo” como lo llama pancakeSwap), definir el token que se desea cambiar y el que se desea recibir, y además debemos especificar cosas básicas como la cantidad de tokens que deseamos intercambiar, el gas estimado que toleramos cancelar, y poco más.

Funciones disponibles en PancakeSwap

Ahora bien, ya entendiendo un poco mejor hacia dónde vamos, ¿cómo sabemos que función llamar, y que variables debemos enviar en función de lo que queremos hacer?, muy simple, y la verdad tenemos 2 formas de averiguarlo, la primera es buscar su contrato en bscscan, e irnos a la pestaña “contrato”, ahí podremos ver las funciones disponibles, y las variables que se necesitan para ser llamadas (puedes verlo directamente desde aquí).

La segunda opción (y mi preferida), es buscar en bscscan el contrato de PancakeSwap, y ver las transacciones que las personas hacen en ella, como por ejemplo esta:

Si hacemos click en “Click to see More”, podremos ver específicamente la función que se utilizó en dicha transacción:

Y si luego seleccionamos “Decode input data”, podremos ver las variables que se enviaron:

Luego de escribir este artículo me di cuenta que existe documentación por parte de pancakeSwap sobre sus funciones detalladamente, las pueden ver aquí.

Ya con esto vemos cómo podemos intercambiar de un token a otro, en este caso intercambiaron 500$ BUSD a un token llamado META, y la función llamada es “swapExactTokensForTokens”. Las variables utilizadas son:

“amountIn”, con un valor de 500000000000000000000 (500 convertidos a Wei)

“amountOutMin”, con un valor de 102045469485932120100 (102.04 convertidos a Wei)

“path”, con 3 contratos diferentes, el primero es el contrato de BUSD, el segundo es de WBNB, y el tercero del token a comprar llamado META, esto quiere decir que primero se pasó de BUSD a WBNB, y luego de WBNB a META (esta variable se define como un array)

“to”, la address a donde queremos que lleguen los tokens intercambiados (nuestra wallet por lo general)

“deadline”, por ultimo tenemos esta variable que es el tiempo máximo que queremos que dure en ejecutarse nuestra operación.

Y en este punto quizás te preguntes, ¿Por qué primero se pasan los BUSD a BNB para poder comprar un token? Y la respuesta es simple, si un token no tiene liquidez en BUSD, primero hay que intercambiar los BUSD por un token que si los tenga, en este caso el token META no tiene liquidez en BUSD, pero si en BNB, por lo que primero se pasan los BUSD a WBNB, y de ahí se cambian los WBNB al token META. Esto hay que tomarlo muy en cuenta a la hora de crear nuestras operaciones si deseamos operar con BUSD en vez de BNB, ya que muchos tokens solo tienen liquidez en BNB.

Recreando la operación desde nuestro bot en C#

Ahora si vamos al grano, vamos a ver cómo podemos intercambiar BUSD por el token META directamente desde nuestro bot, usando PancakeSwap como plataforma de intercambio. Vamos a intercambiar 20 BUSD por META, que al precio actual vendrían siendo unos 5 META.

static async Task DeTokenAToken()
        {
            try
            {
                var url = "https://bsc-dataseed.binance.org/";
                var myWallet = "Direccion/AddressDeTuWalletAQUI";
                var privateKey = "KeyPrivadaDeTuWalletAQUI";
                var contractAddress = "0x10ED43C718714eb63d5aA57B78B54704E256024E"; //CONTRATO DE PANCAKESWAP 2.0

                var account = new Account(privateKey, 56);
                var web3 = new Web3(account, url);
                var transactionManager = web3.TransactionManager;

                var swapHandler = web3.Eth.GetContractTransactionHandler<swapExactTokensForTokensFunction>();

                var camtidadBUSD = Web3.Convert.ToWei("20");//CANTIDAD DE BUSD A INTERCAMBIAR - 20$
                var camtidadToken = Web3.Convert.ToWei("2");//MINIMO DE TOKEN META A RECIBIR - 5 META
                var swapDTO = new swapExactTokensForTokensFunction()
                {
                    AmountIn = camtidadBUSD,
                    AmountOutMin = camtidadToken, //MINIMO DE TOKENS A RECIBIR
                    Path = new List<string>
                {
                    "0xe9e7cea3dedca5984780bafc599bd69add087d56", //CONTRATO BUSD
                    "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", //CONTRATO WBNB
                    "0x04073d16c6a08c27e8bbebe262ea4d1c6fa4c772" //CONTRATO TOKEN META
                },
                    To = myWallet,
                    Deadline = ((DateTimeOffset)DateTime.Now).ToUnixTimeSeconds() + 260,
                    GasPrice = 5000000000,
                    Gas = 290000
                };


                var transactionSwapReceipt = await swapHandler.SendRequestAndWaitForReceiptAsync(contractAddress, swapDTO);

                Console.WriteLine("Transaction hash is: " + transactionSwapReceipt.TransactionHash);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception Message: " + ex.Message);
            }
        }
        public partial class swapExactTokensForTokensFunction : swapExactTokensForTokensBase { }

        [Function("swapExactTokensForTokens", "uint256[]")]
        public class swapExactTokensForTokensBase : FunctionMessage
        {
            [Parameter("uint256", "amountIn", 1)]
            public virtual BigInteger AmountIn { get; set; }
            [Parameter("uint256", "amountOutMin", 2)]
            public virtual BigInteger AmountOutMin { get; set; }
            [Parameter("address[]", "path", 3)]
            public virtual List<string> Path { get; set; }
            [Parameter("address", "to", 4)]
            public virtual string To { get; set; }
            [Parameter("uint256", "deadline", 5)]
            public virtual BigInteger Deadline { get; set; }
        }

Bien, como podemos observar, hemos definido todas las variables antes mencionadas, el monto a enviar, el mínimo de tokens a recibir, los 3 contratos que usará nuestra transacción (BUSD – WBNB – TOKEN), y el Gas que estamos dispuestos a cancelar. El gas es solo una estimación, la transacción solo gastará el monto exacto que exija la red en dicho momento, aconsejo dejarlo tal cual como esta.

Al final del código podemos ver donde definimos la función a usar, en este caso:

[Function("swapExactTokensForTokens", "uint256[]")]

Si en cambio quisiéramos comprar nuestro token usando directamente WBNB en vez de BUSD, tendríamos que usar:

[Function("swapExactETHForTokens", "uint256[]")]

Eliminando el contrato BUSD del array “path”, donde nos quedaría solo el contrato de WBNB y el token a comprar, ademas de modificar la variable “AmountIn”, donde colocaremos el monto en BNB y no en BUSD.

Hay otra función que quisiera mencionar, ya que puede llegar a confundir a más de uno a la hora de usarla, y es esta:

[Function("swapExactTokensForETHSupportingFeeOnTransferTokens", "uint256[]")]

O en su defecto esta:

[Function("swapExactETHForTokensSupportingFeeOnTransferTokens", "uint256[]")]

Esta función se usa para intercambiar tokens que incluyan comisiones propias, por ejemplo, hay tokens que a la hora de hacer el swap, realizan su propia quema dentro de la transacción, como si fuese una especie de comisión, en estos tokens por lo general se usa un slippage alto, de 10% o más, justamente para cubrir esa tarifa adicional.

Hasta donde he observado, estas 2 funciones (y sus derivadas) se pueden usar incluso para tokens que no exijan ninguna comisión adicional, y nos puede simplificar las cosas al no tener que programar tantas condiciones diferentes para cada caso.

Enviar tokens de una Wallet a otra

Ahora veremos cómo enviar tokens de una wallet a otra, en esta parte no me extenderé mucho ya que es bastante simple, definiremos nuestra key privada, el monto a enviar y la dirección de destino.

public async System.Threading.Tasks.Task transfer2Async() {
            try
            {
                var privateKey = "TuKEYPRIVADA";
                var account = new Account(privateKey, 56);
                var camtidadBNB = Web3.Convert.ToWei("0,01");//MONTO A ENVIAR
                string hexBNB = camtidadBNB.ToString("X2");
           
                Console.WriteLine("Our account: " + account.Address);

                var web3 = new Web3(account, "https://bsc-dataseed.binance.org/");

                var toAddress = "DIRECCIONARECIBIR";//DIRECCION QUE VA A RECIBIR
                var transactionManager = web3.TransactionManager;

                var txnInput = new TransactionInput()//PRIMERA TRANSACCION PARA ESTIMAR EL GAS
                {
                    From = account.Address,
                    To = toAddress,
                    Value = new HexBigInteger(hexBNB),
                };

                var gasEstimate = await transactionManager.EstimateGasAsync(txnInput);
                Console.WriteLine("gas estimado " + gasEstimate.Value);
                txnInput.Gas = gasEstimate;

                string hexGas = gasEstimate.Value.ToString("X2");

                var txnInput2 = new TransactionInput()//SEGUNDA TRANSACCION, LA DEFINITIVA
                {
                    From = account.Address,
                    To = toAddress,
                    Gas = new HexBigInteger(hexGas),
                    Value = new HexBigInteger(hexBNB),
                };

                var gasPrice = await web3.Eth.GasPrice.SendRequestAsync();
                
                txnInput2.GasPrice = new HexBigInteger(gasPrice.Value + Web3.Convert.ToWei(1, UnitConversion.EthUnit.Gwei));

                var txnHash2 = await transactionManager.SendTransactionAsync(txnInput2);

                Console.WriteLine(txnHash2);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception Message: " + ex.Message);
            }
        }

En esta transacción podemos estimar el gas actual de la red BSC, por lo que primero se realiza una operación de prueba para que nos devuelva la estimación, y luego con dicho valor realizamos la transacción final. Aquí no hace falta colocar nuestra dirección propia, ya que se obtiene directamente usando la Key Privada.

Y ya con esto damos por terminado nuestro tutorial, mas adelante podría agregar como obtener el precio en tiempo real, de un token directo de pancakeSwap sin utilizar su API (que por lo general tiene bastante delay), pero esto dependerá del apoyo que reciba este post. Si tienes alguna duda solo déjala en los comentarios y con gusto la responderé.