🍌
EVM-XCM Jungle Toolkit
  • πŸ“—Introduction
    • 🌴What is the EVM-XCM Jungle Toolkit?
    • 🧐Why use this toolkit?
    • πŸ‘ͺWho is this toolkit made for?
    • βš’οΈWhat tools are this toolkit made of?
  • ↔️Build an EVM+Substrate wallet compatible architecture in your dApp
    • ⁉️Two different standards
      • ℹ️Addresses Format
      • 🎭Public Key vs Hash-based
      • πŸ“©Multichain compatibility
      • ⚑Account Types
    • β˜‘οΈRecommended wallets
      • 🦊MetaMask
      • ☸️SubWallet
      • 🀚Talisman
    • 🟒The EVM address standard
      • πŸ”Get an address for a Signer
        • πŸ‘οΈCheck the validity of the address
      • πŸ’°Get the native balance of a Signer
        • Get an ERC20 token's balance
      • πŸŽ‡Get the chain information for a Signer
    • πŸ”΅The Substrate address standard
      • πŸ”Get an address for a Signer
        • πŸ‘οΈCheck the validity of the address
      • πŸ’°Get the native balance of a Signer
      • πŸŽ‡Get the chain information for a Signer
    • πŸ“§Mapping of addresses
    • 🀝Implement multiple wallet support on your dApp front-end
      • πŸ’šUnderstanding the EVM provider/signer concepts
      • ❀️Understanding the Keyring concept
      • πŸ”‘Sign and send EVM transactions
      • πŸ—οΈSign and send Substrate extrinsics
    • πŸ“šRessources
  • ➑️Build a cross-chain transaction from any EVM-chain to a Substrate-based chain using IBC and XCM
    • ⁉️Understanding the flow
    • πŸ¦‘Transfer tokens to Moonbeam Parachain using Squid SDK from any EVM chain
    • πŸͺCreate & execute a transaction on the source chain before bridging tokens (pre-hook)
    • πŸͺCreate & execute a transaction on Moonbeam after bridging tokens (post-hook)
    • ✨Create & execute a swap using Stellaswap pools to obtain xcTokens
    • πŸ™ŒWrap-up
    • πŸ“šRessources
  • ⬅️Build a cross-chain transaction from a Substrate-based chain to Moonbeam chain
    • ⁉️Understanding the flow
    • πŸ’‘Using LightSpell API to generate valid XCM calls
    • πŸŒ‰Transfer tokens between parachains (or relay chain) using XCM and ParaSpell SDK
    • πŸ™ŒWrap up
    • πŸ“šRessources
  • ⏩Build batches on source and destination chains using Squid and batch precompile
    • ⁉️Understanding the flow
    • πŸ‘ŒUnderstanding the batch precompile
    • βœ…Create a batch transaction compatible with Squid router
    • πŸͺIntegrate a batch into a Hook using Squid
    • πŸ™ŒWrap up
    • πŸ“šRessources
  • πŸ”Build a gas-less transaction from/to an EVM/Substrate chain using callPermit
    • ⁉️Understanding the flow
    • πŸ”‹Generating the data to sign
    • ✍️Collecting the signature
    • 🌐Relaying the signature
    • ⚑Executing the transaction
    • πŸ™ŒWrap up
    • πŸ“šRessources
  • πŸ”€Build a custodial solution to make your dApp wallet-less & gas-less using EIP-712
    • ⁉️Understanding the flow
    • ⛓️Smart contract
    • βš™οΈBack end
  • πŸ”½Examples of working cross-chain dApps made using the Jungle Toolkit
    • 🌴Case Study: The Great Escape
      • The Player Structure
      • The xDeposit Function
      • The xRegister Function
Powered by GitBook
On this page
  • The role of the back-end in a wallet-less dApp
  • Example of a simple Express POST function
  • Example of a function that encodes, sign, relay/executes a transaction
  1. Build a custodial solution to make your dApp wallet-less & gas-less using EIP-712

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 1 year ago

πŸ”€
βš™οΈ