AptosVM <> MEVM

In this guide you will learn how to send transactions between AptosVM and MEVM.

Overview

Welcome to the tutorial for using Movement Labs' blockchain and the Move language! In this tutorial, we will cover two parts:

Part 1: Signing Multi-Party Transactions with Multi-Signature Accounts - including an MEVM multisig Safe Wallet!

Part 2: Calling an EVM Smart Contract from an AptosVM Wallet

Requirements

  • Node.js

  • npm

Setup

Clone our demo repository to access the necessary files:

git clone https://github.com/movemntdev/movement_interop_demo

Access the app directory:

cd movement_interop_demo

Install all dependencies:

npm install

Open your favorite IDE. We recommend using VSCode:

code .

You will find an .env file in the app directory. Most variables are already filled in, but pay attention to MOVE_PRIVATE_KEY and ETHEREUM_PRIVATE_KEY. Make sure you use private keys for wallets that have been funded. You can use our Faucet to fund them.

Note: The following variables are needed for the demo:

  • MOVE_MULTISIG_OTHER_OWNER_ADDR

  • MOVE_FRAMEWORK

  • EVM_PRECOMPILE_CONTRACT

After setting your private keys, let's take a look at our setup. At the top, we import our dependencies:

  • ethers: Ethereum wallet and utilities

  • @safe-global/protocol-kit: Toolkit for interacting with the Safe protocol

  • aptos: Library for interacting with the Aptos protocol

  • dotenv: Module for loading environment variables from a .env file into process.env

const { ethers, getDefaultProvider } = require('ethers');
const { EthersAdapter, SafeFactory } = require('@safe-global/protocol-kit');
const Safe = require('@safe-global/protocol-kit').default;
const { AptosAccount, BCS, AptosClient, HexString, TxnBuilderTypes } = require("aptos");
const dotenv = require("dotenv");
dotenv.config();

Part 1: MEVM to AptosVM

Head to mevm-to-aptosvm.js. You will find several functions. Feel free to explore them and understand what they do.

  • deploySafe(): Deploys a new, MEVM Safe contract

  • setupMoveMultisigAccount(safeAddress): Sets up a new Move multisig account

  • vote(safeAddress, multiAccount): Votes on a a Move proposal from a Safe contract

  • checkSafeContractVoted(safeAddress, multiAccount): Checks if a Safe contract has been voted on

  • submitTx(rawTxn): Submits a transaction

  • zeroPad(multiAccount): Pads with zeroes if the multiAccount address has a different size then the Move address type.

Now, all you have to do is run:

node mevm-to-aptosvm.js

Essentially, the following is what the script will perform:

  1. Creates a Gnosis Safe smart contract using the EVM wallet.

  2. Creates a multisig account using the Move wallet, and sets the Gnosis Safe contract created in step 1 as one of the owners.

  3. Creates a multisig transaction (the content can be arbitrary, but for this demo we are adding an owner to the multisig account) and votes using the Move wallet.

  4. Creates a transaction using the EVM wallet to vote for the transaction created in the previous step in the Move multisig account.

  5. Proposes and confirms the transaction created by the EVM wallet to the deployed Safe service.

  6. Executes the transaction the confirmed transaction.

  7. Checks whether the EVM multisig account has voted successfully.

As you can see, we are interfacing between AptosVM and MEVM when we finally sign a Move transaction from the EVM multisig. To do so, we have to pay attention to the vote() function:

async function vote(safeAddress, multiAccount) {
    (...)
    // 1.Encodes the predefine vote calldata
    // function vote(bytes32 multisigAccount, uint sequenceNumber, bool approve)
    let calldata = safeInterface.encodeFunctionData("vote", [multiAccount, 1, true]);

    // 2.Encodes the callMove function present in the Precompile contract
    let txdata = precompileInterface.encodeFunctionData("callMove", [process.env.MOVE_FRAMEWORK, calldata])
    let safeTransactionData = {to: process.env.EVM_PRECOMPILE_CONTRACT, data: txdata, value: 0}

    // 3.creates a safe transaction
    const safeTransaction = await safeSDK.createTransaction({ safeTransactionData })
    
    // 4. signs the safe tx
    const safeTxHash = await safeSDK.getTransactionHash(safeTransaction)
    const senderSignature = await safeSDK.signTransactionHash(safeTxHash)
    
    // 5. sends the safe tx to the api kit
    await safeService.proposeTransaction({safeAddress,safeTransactionData: safeTransaction.data, Hash,senderAddress: await wallet.getAddress(),senderSignature: senderSignature.data,})
    await safeService.confirmTransaction(safeTxHash, senderSignature.data)
    
    // 6. execute the safe tx
    const executeTxResponse = await safeSDK.executeTransaction(safeTransaction)
    (...)
}

To interact between AptosVM and MEVM, pay attention to the vote() function, where we encode a Move transaction and pass it to the callMove() precompile function, which serves as the entry point for calling Move functions from MEVM.

Part 2: AptosVM to MEVM

Now, head to aptosvm-to-mevm.js. You will find a tighter script:

  • deployNumberRegistry(): Deploys a new Solidity NumberRegistry smart contract

  • submitTx(rawTxn): Submits an AptosVM transaction

  • getNonce(addr): gets the EVM nonce of an AptosVM Account.

  • setNumber(contract): Calls the setNumber function present in the NumberRegistry smart contract

Now you can run the script:

aptosvm-to-mevm.js

This is what the script will perform:

  1. Creates a NumberRegistry.sol smart contract using the EVM wallet.

  2. Calls the setNumber() function of the NumberRegistry contract using the Move wallet.

  3. Checks whether the number was set successfully.

Here we should pay special attention to the setNumber() function:

async function setNumber(contract) {
	// 1. Encodes the EVM function
	let calldata = interface.encodeFunctionData("setNumber", [100]);
	
	// 2. Gets the AptosVM Account EVM nonce
	let nonce = await getNonce(owner.address())
	
	// 3. Generates the AptosVM transaction that interacts with the EVM contract
	let txn = await client.generateTransaction(owner.address(), {
		function: `0x1::evm::send_move_tx_to_evm`,
		type_arguments: [],
		arguments: [nonce, new HexString(contract.address).toUint8Array(), BCS.bcsSerializeU256(0), new HexString(calldata).toUint8Array(), 1],
	});
}

Here we are encoding an EVM function and passing it to the 0x1::evm::send_move_tx_to_evm function present in our evm framework.

In the setNumber() function, we encode an EVM function and pass it to the 0x1::evm::send_move_tx_to_evm function in our EVM framework.

Conclusion

That concludes our tutorial! We've covered signing multi-party transactions with multi-signature accounts from an EVM multisig and calling EVM smart contracts from an AptosVM wallet.

Feel free to reach out to us on Discord if you have any issues, questions or feedback. We would love to hear from you!

Last updated