¿Cuál es la mejor forma de canje de tokens por parte del usuario?

Fondo

RETN Deals ofrece cupones de descuento de marcas globales y, además, ofrece un generoso reembolso en Criptomoneda (RETN). La aplicación web rastrea automáticamente las compras de los consumidores en estas tiendas y las recompensa en criptografía. Una vez que el usuario alcanza el límite mínimo de retiro que se establece dentro de la aplicación web, se vuelve elegible para canjear RETN.

Buscando el mejor enfoque para manejar el proceso de redención.

Formas de acercamiento

Enfoque 1.

Cada vez que el usuario hace clic en Canjear, la aplicación web llama al método token.transfer y transfiere el token de la dirección de RETN Deal a la dirección ERC20 del usuario.

Beneficios

  • La validación ocurre en el lado del servidor, el usuario no puede manipular más valor del que es elegible.

Problemas

  • Precio de la gasolina: el propietario de la aplicación web tiene que pagar el precio de la gasolina por cada uno de los canjes. El costo consumido en la transferencia de tokens a los usuarios finales es muy alto
  • Cola: las transacciones de cada usuario se obstruyen hasta que se borran las transacciones anteriores. Esto se debe a que el NONCE se incrementará para cada transacción, ya que se originan en la misma dirección.

Enfoque 2

Cree un contrato inteligente que el usuario pueda llamar (a través de metamask)

Beneficios

  • Rentable: El consumidor es el portador del precio del gas

  • Cola: el origen de la transacción será diferente para cada cliente y, por lo tanto, la transacción será rápida

  • Elección del cliente: El cliente puede elegir cuánto retirar y cuándo retirarlo. Él / ella también puede controlar el precio del gas.

Problemas

  • El límite de retiro debe establecerse cada vez en el contrato para que el usuario no pueda retirar más de lo que merece. Esto consume gasolina. Incluso si se establece una matriz de elementos, la cantidad de elementos en una matriz no puede ser superior a 180 (aprox.) debido al límite de gas de bloque/transacción, por lo tanto, se necesitará una mayor cantidad de transacciones para establecer el límite, lo que nuevamente consumirá el gas del remitente y las transacciones permanecen pendientes en la cola hasta que se extraen las transacciones de su antepasado.

  • Si el límite de retiro del usuario no está establecido, no podrá canjear

  • Si vamos a permitir que los usuarios canjeen sin establecer el límite, el usuario puede retirar más valor del que es elegible.

¿Cuál es un mejor enfoque para permitir que los consumidores canjeen RETN, que puede crear valor para el consumidor, mantener la seguridad del contrato, reducir el consumo de gas por RETN y no obstruir la cadena de bloques?

¿Oraclize debería ser de alguna ayuda para este tipo de problemas? Supongo que seguirá consumiendo gasolina.

Respuestas (1)

Encontró que la mejor manera es hacer que el receptor pague por la transacción. Siguió a Steve Marx en este artículo para llegar al siguiente código. El flujo del código será como a continuación.

  • Los usuarios actualizan su dirección de billetera preferida en la aplicación
  • Los usuarios se aseguran de tener ETH para gasolina y la dirección de la billetera cargada en metamask
  • En el lado del servidor, calculando cuántos tokens se supone que deben recibir y luego firma la transacción

código de servidor de nodo

const recipient = receipientaddress;
const amount =  web3.toWei(tokensToSend);
const nonce = req.body.nonce; //calucated nonce of the wallet address (of metamask)
const tmpContractAddress = "0x...";//contract address that will send the token (address of the deployed contract below) 
var hash = "0x" + ethereumjs.soliditySHA3(
    ["address", "uint256", "uint256", "address"],
    [recipient,amount, nonce, tmpContractAddress]
  ).toString("hex");
var signed = web3.personal.sign(hash, senderAddr, senderAddrPass);

código de contrato inteligente

pragma solidity ^0.4.20;

/**
 * @title Ownable
 * @dev The Ownable contract has an owner address, and provides basic authorization control
 * functions, this simplifies the implementation of "user permissions".
 */
contract Ownable {

  address public owner;
  event OwnershipTransferred (address indexed _from, address indexed _to);

  /**
   * @dev The Ownable constructor sets the original `owner` of the contract to the sender
   * account.
   */
  function Ownable() public{
    owner = msg.sender;
    emit OwnershipTransferred(address(0), owner);
  }

  /**
   * @dev Throws if called by any account other than the owner.
   */
  modifier onlyOwner() {
    require(msg.sender == owner);
    _;
  }

  /**
   * @dev Allows the current owner to transfer control of the contract to a newOwner.
   * @param newOwner The address to transfer ownership to.
   */
  function transferOwnership(address newOwner) public onlyOwner {
    require(newOwner != address(0));
    owner = newOwner;
    emit OwnershipTransferred(owner,newOwner);
  }
}

/**
 * @title Token
 * @dev API interface for interacting with the Token contract 
 */
interface Token {
  function transfer(address _to, uint256 _value) external returns (bool);
  function balanceOf(address _owner) constant external returns (uint256 balance);
}

contract RetnWithdraw10 is Ownable{
    address owner;
    Token token;
    mapping(address => mapping(uint256 => bool)) usedNonces;
    event Redeemed(address indexed beneficiary, uint256 value);

    function RetnWithdraw10() public {
        address _tokenAddr = 0x815CfC2701C1d072F2fb7E8bDBe692dEEefFfe41;//0x2ADd07C4d319a1211Ed6362D8D0fBE5EF56b65F6; 
      // test token 0x815CfC2701C1d072F2fb7E8bDBe692dEEefFfe41;
      token = Token(_tokenAddr);
        owner = msg.sender;
    }

    function claimPayment(uint256 amount, uint256 nonce, bytes sig) public {


        require (token.balanceOf(this) >= amount);
        require (!usedNonces[msg.sender][nonce]);

        // This recreates the message that was signed on the client.
        bytes32 message = prefixed(keccak256(msg.sender, amount, nonce, this));

        require (recoverSigner(message, sig) == owner);


        usedNonces[msg.sender][nonce] = true;

        //msg.sender.transfer(amount);
        if(token.transfer(msg.sender,amount)){
           emit Redeemed(msg.sender,amount);
      }
      else
      usedNonces[msg.sender][nonce] = false;
    }

    // Destroy contract and reclaim leftover funds.
    function kill() public onlyOwner{
        uint256 remaining = token.balanceOf(this);
        if(remaining>0)
            token.transfer(owner,remaining);
        selfdestruct(msg.sender);
    }


    // Signature methods

    function splitSignature(bytes sig)
        internal
        pure
        returns (uint8, bytes32, bytes32)
    {
        require(sig.length == 65);

        bytes32 r;
        bytes32 s;
        uint8 v;

        assembly {
            // first 32 bytes, after the length prefix
            r := mload(add(sig, 32))
            // second 32 bytes
            s := mload(add(sig, 64))
            // final byte (first byte of the next 32 bytes)
            v := byte(0, mload(add(sig, 96)))
        }

        return (v, r, s);
    }

    function recoverSigner(bytes32 message, bytes sig)
        internal
        pure
        returns (address)
    {
        uint8 v;
        bytes32 r;
        bytes32 s;

        (v, r, s) = splitSignature(sig);

        return ecrecover(message, v, r, s);
    }

    // Builds a prefixed hash to mimic the behavior of eth_sign.
    function prefixed(bytes32 hash) internal pure returns (bytes32) {
        return keccak256("\x19Ethereum Signed Message:\n32", hash);
    }
}

y finall en el lado html (front-end)

tokensToRedeem = 2; //should be same as what is passed to the server
        var abi = [ { "constant": false, "inputs": [ { "name": "amount", "type": "uint256" }, { "name": "nonce", "type": "uint256" }, { "name":  "sig", "type": "bytes" } ], "name": "claimPayment", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" } ];
        var MyContract = web3.eth.contract(abi);
        var contrctAddress = "0x...";//deployed contract address
        var myContractInstance = MyContract.at(contrctAddress);
        web3.eth.getAccounts(function(error, result) { 
            if(!error)
            {
                if(result.length == 0)
                {
                    toastr.error('Please ensure you logged in metamask.');
                    return false; 
                }
                myContractInstance.claimPayment(web3.toWei(tokensToRedeem),2,hash,{ from:result[0],
                            to:contrctAddress,
                            value:  0, 
                        }, function(err, transactionHash) {
                            if (!err)
                            {
                            $('#btnMetaMask').css('display','none');
                                $('#notesMeta').html("Transaction Submitted. Track <a target='_blank' href='<?php echo $etherscanurl;?>/tx/"+transactionHash +"'>here</a>");
                                console.log(transactionHash); 
                            }
                            else
                            {
                                console.log('Error While Redeem');
                                console.log(error);
                            }; 
                    }) 
                } 
                else
                { console.log('error');
                    console.log(err);
                }
            });