Transacciones sin gas: envío de transacciones en lugar de mensajes de firma. ¿Como funciona?

Estoy siguiendo este tutorial: https://docs.openzeppelin.com/learn/sending-gasless-transactions

Pero en realidad no explica claramente cómo funciona en detalles. Una transacción estándar es bastante simple de entender. Un usuario firma un mensaje, lo envía, lo paga y va a blockchain.

Si hay una transacción sin gas en la red principal o en la red de prueba como ropsten, el usuario no envía la transacción a la cadena de bloques, sino que solo la firma. ¿Qué pasa después? Debe haber algo que tome el mensaje personal firmado del usuario (porque, hasta donde yo sé, este mensaje es "privado", no va a la red global) y lo pone en la red y lo paga, pero ¿cómo lo hace exactamente? ¿trabajar?

Por un momento estuve pensando que si realizo un contrato que extiende GSNRecipient cualquier transacción de usuario cambiará a solicitud de firma y este contrato realizará transacciones a la red. Así que recreé un contrato de contador simple que amplía GSNRecipientUpgradeSafe.

pragma solidity ^0.5.0;
import "./GSNRecipient.sol";
contract Counter is GSNRecipientUpgradeSafe
{
    int public value;
    
    function addValue()
        external
    {
        value++;
    }
    
    function start()
        external
    {
        __GSNRecipient_init();
    }
    
    function acceptRelayedCall(
        address relay,
        address from,
        bytes calldata encodedFunction,
        uint256 transactionFee,
        uint256 gasPrice,
        uint256 gasLimit,
        uint256 nonce,
        bytes calldata approvalData,
        uint256 maxPossibleCharge
    )
        external
        view
        returns (uint256, bytes memory)
    {
        return _approveRelayedCall();
    }

    function _preRelayedCall(bytes memory context) internal returns (bytes32)
    {

    }

    function _postRelayedCall(bytes memory context, bool success, uint256 actualCharge, bytes32 preRetVal) internal
    {
        
    }
}

Lo implementé y llamé a start()

No quería usar ningún script de reacción. Decidí usar una biblioteca web3js normal y un sitio web simple muy básico.

<span id="cnt">COUNTER</span>
<button onclick="getContract().methods.addValue().send({from:account})">click</button>
<script>
    let account;
    let web3;
    window.addEventListener('load', async () => {
        if(window.ethereum)
        {
            try
            {
                await ethereum.enable();
                web3 = new Web3(ethereum);
                web3.eth.defaultAccount = (await web3.eth.getAccounts())[0];
                account = web3.eth.defaultAccount;
                let c = getContract();
                $("#cnt").html(await c.methods.value().call());
                
            }
            catch(e)
            {
                console.log(e.toString());
            }
        }
    });
    
    function getContract()
    {
        return new web3.eth.Contract(abi, "0xaa9f0c1AC580EFA7A0e6d64d3eEBF62B4F970701");
    }
</script>

Revisé una demostración de gsn: https://metacoin.opengsn.org/ Después de hacer clic en "enviar metacoin", recibo una solicitud de mensaje de firma. En el código fuente hay contract.methods normales......send(). Así que decidí recrearlo.

Pero desafortunadamente:

<button onclick="getCounterContract().methods.addValue().send({from:account})">click</button>

Esto simplemente llama a una transacción normal en lugar de una solicitud de firma.

web3.versión: "1.2.6"

Estoy bastante seguro de que entendí mal algo o no entendí correctamente. ¿Cómo puedo hacer que esto funcione? ¿Quizás haya algo mejor que el código de red GSN de OpenZeppelin?

Estoy usando metamáscara.

Respuestas (1)

La idea de las transacciones "sin gas" es que un usuario firme un mensaje y envíe ese mensaje a un repetidor de gas . El repetidor es una entidad separada, que recopila los mensajes de los usuarios y envía las transacciones. De esta manera, el usuario no necesita pagar por la transacción, solo lo hace el retransmisor. Luego, opcionalmente, el usuario puede pagar en tokens u otros medios de pago al repetidor.

Los fondos de los usuarios se almacenan en un contrato inteligente, y solo con una firma válida proporcionada por el usuario, se pueden enviar fondos desde ese contrato. En su ejemplo, está llamando a una función en un contrato como usuario. Dado que todas las transacciones reales en la red requieren gas, esto simplemente enviará una transacción directamente al contrato, en lugar de usar un repetidor de gas.

Para enviar transacciones a la red de estaciones de servicio, puede usar sus bibliotecas de JavaScript, por ejemplo:

const { RelayProvider } = require('@opengsn/gsn')

const provider = new RelayProvider(web3.currentProvider, ...);
const web3 = new Web3(provider);

const counterContract = new web3.eth.Contract(abi, ...);
await counterContract.methods.addValue().send({ ... });

Este usará el RelayProviderpara firmar la transacción y enviarla a un repetidor de gas, quien luego enviará la transacción a su contrato, en nombre del usuario.

Hay una guía más detallada para usar OpenGSN aquí: https://docs.opengsn.org/gsn-provider/getting-started.html