βš™οΈBack end

In order to be able to relay transaction we need a centralized back-end able to relay the transactions on behalf of users.

  • Example of relaying a transaction

  • Reading a player structure (simple GET API endpoit)

The role of the back-end in a wallet-less dApp

In the previous section (Build a gas-less transaction), we learned how to use Moonbeam's callPermit in order to relay a transaction and make it gassless. In order to go one step further and removing the need to sign a transaction, the transaction is going to be generated by the mirror wallet and dispatched by the contract's owner (or any authorized address defined in a particular modifier).

The backend is going to generate the data to sign and execute it on behalf of the user, following for instance a web2 POST call.

Example of a simple Express POST function

app.post('/doTx', async (req, res) => {
  let infos = await req.body
  let sendTxn = await prepareCall(infos)
    if (await sendTxn == true) {
      await res.status(200).json({ 'ok': true, 'message': 'Tx successfully sent!' })
    }
    else {
      await res.status(200).json({ 'ok': false, 'message': 'There was a problem during the tx' })
    }
})

Example of a function that encodes, sign, relay/executes a transaction

// dependencies
let ethers = require('ethers');
let {
    signTypedData,
    SignTypedDataVersion
} = require('@metamask/eth-sig-util');

async function prepareCall(userData) {
    let dataFct
    let messageData
    let finalCall
    let formattedSignature = {}

    // first we use the real EOA address received in the post
    let wallet = userData.userWallet
    // we then get its mirror's wallet private key
    let key = await getPvKey(wallet) // here you need to create a function able to get the mirror wallet private key using your own security standard
    let userSigner = new ethers.Wallet(await key, provider); // internal wallet signer
    let publicAddy = await userSigner.getAddress() // internal wallet address
    let currentNonce = await getNonces(publicAddy) // useful function made by Moonbeam to get the current Nonces for an address (see helpers below)

    // here we're encoding the function using the required arguments
    let iface = new ethers.utils.Interface(yourABI);
    dataFct = iface.encodeFunctionData("yourSolidityFunction",
        [
            arg1,
            arg2,
            arg3
        ])
    userData.encoded = dataFct

    // function to generate compliant message for callPermit
    const createPermitMessageData = async function() {
        const message = {
            from: publicAddy,
            to: yourContractAddress,
            value: 0,
            data: userData.encoded,
            gaslimit: 300000,
            nonce: currentNonce.toNumber(),
            deadline: Date.now() + (5 * 60 * 1000),
        };
        const typedData = {
            types: {
                EIP712Domain: [{
                    name: 'name',
                    type: 'string'
                }, {
                    name: 'version',
                    type: 'string'
                }, {
                    name: 'chainId',
                    type: 'uint256'
                }, {
                    name: 'verifyingContract',
                    type: 'address'
                }, ],
                CallPermit: [{
                    name: 'from',
                    type: 'address'
                }, {
                    name: 'to',
                    type: 'address'
                }, {
                    name: 'value',
                    type: 'uint256'
                }, {
                    name: 'data',
                    type: 'bytes'
                }, {
                    name: 'gaslimit',
                    type: 'uint64'
                }, {
                    name: 'nonce',
                    type: 'uint256'
                }, {
                    name: 'deadline',
                    type: 'uint256'
                }, ],
            },
            primaryType: 'CallPermit',
            domain: {
                name: 'Call Permit Precompile',
                version: '1',
                chainId: 1284,
                verifyingContract: '0x000000000000000000000000000000000000080a',
            },
            message: message,
        };
        return {
            typedData,
            message,
        };
    };

    try {
        messageData = await createPermitMessageData();
    } catch (errMd) {
        console.log(errMd)
    }
    
    // sign the message
    let signature
    try {
        signature = signTypedData({
            privateKey: Buffer.from(key, 'hex'),
            data: await messageData.typedData,
            version: SignTypedDataVersion.V4,
        });
        console.log(`Signature successful with hash: ${signature}`);
    } catch (signErr) {
        console.log(signErr, 'signErr')
    }

    try {
        // validate the message
        let {
            r,
            s,
            v
        } = await getSignatureParameters(signature); // see helpers below
        formattedSignature = {
            r: r,
            s: s,
            v: v,
        };

    } catch (sigError) {
        console.log(sigError)
    }

    try {
        finalCall = await {
            'message': await messageData.message,
            'signature': await formattedSignature,
            'config': await userData
        }
    } catch (err) {
        console.log(err)
    }

    try {
        let deployerKey = "yourPrivateKey" // only for development purpose, don't do that on production
        let deployerSigner = new ethers.Wallet(deployerKey, provider);
        const preCompileContract = new ethers.Contract(callPermitAddress, callPermitABI, deployerSigner)
        let tx = await preCompileContract.dispatch(
            finalCall.message.from,
            finalCall.message.to,
            finalCall.message.value,
            finalCall.message.data,
            finalCall.message.gaslimit,
            finalCall.message.deadline,
            finalCall.signature.v,
            finalCall.signature.r,
            finalCall.signature.s)
        let result = await tx.wait()
        await console.log('Tx succesfull!')
        return true
    } catch (err) {
        console.log(err)
        return false
    }
}

// useful helpers made by Moonbeam team
async function getSignatureParameters(signature) {
    if (!ethers.utils.isHexString(signature)) {
        throw new Error(
            'Given value "'.concat(signature, '" is not a valid hex string.')
        );
    }
    var r = signature.slice(0, 66);
    var s = "0x".concat(signature.slice(66, 130));
    var v = "0x".concat(signature.slice(130, 132));
    v = ethers.BigNumber.from(v).toNumber();
    if (![27, 28].includes(v)) v += 27;
    return {
        r: r,
        s: s,
        v: v
    };
};

async function getNonces(wallet) {
    const preCompileContract = new ethers.Contract(callPermitAddress, callPermitABI, provider)
    let nonce
    try {
        nonce = await preCompileContract.nonces(wallet);
    } catch (nonceErr) {
        console.log(nonceErr, 'nonce error')
    }
    return await nonce
}

Last updated