[ToC] ## Intro The MEVM, an adaptation of the EVM, is tailored with specialized precompiles to support MEV use cases, allowing developers to craft MEV applications as smart contracts within an environment akin to the conventional EVM. As a cornerstone of the SUAVE chain, the MEVM not only significantly reduces barriers to devising new MEV applications, but also facilitates the transformation of centralized infrastructures into decentralized blockchain-based smart contracts. To get a better understanding of how the MEVM works, let's delve into the deployment of a simple version of [`mev-share`](https://github.com/flashbots/mev-share), a protocol for orderflow auctions, defined via smart contract on SUAVE. Our journey below will guide you through the steps of deploying simple mev-share and block builder contracts, interacting with them, and ultimately seeing a block land onchain. ## Prerequisites 🛠 This walkthrough will be based on a script located inside the [suave cli tool](https://github.com/flashbots/suave-geth/blob/suave-poc/cmd/suavecli/testDeployAndShare.go) so don't worry about copying over all of the code. To follow along and use the tool you will need: - [SUAVE node and SUAVE execution node setup](https://github.com/flashbots/suave-geth/tree/main/suave). - Basic knowledge of the Ethereum's Golang libraries and the [Ethereum RPC methods](https://github.com/flashbots/suave-geth/tree/main/suave). Ensure these details are on hand: - `suave_rpc` : address of suave rpc - `goerli_rpc` : address of goerli execution node rpc - `goerli_beacon_rpc` : address of goerli beacon rpc - `ex_node_addr` : wallet address of execution node - `privKeyHex` : private key as hex (for testing) - `relay_url` : address of boost relay that the contract will send blocks to ## Walkthrough Overview 🚀 This walkthrough will go over the process of : 1. deploying the simple `mev-share` auction as a contract 2. deploying a `mev-share` compatible block builder contract 3. sending a transction to `mev-share` 4. sending a backrun transction to `mev-share` 5. build block and send to relay ### 1. Deploy Simple MEV-Share Contract 📜 Our first step is to deploy the [compiled byte code](https://github.com/flashbots/suave-geth/blob/suave-poc/cmd/suavecli/deployMevShareByteCode.go) from our `mev-share` contract. As you will see, deploying on SUAVE feels just like deploying on any other EVM chain. First we gather our transaction details, nounce and gas price, sign the transaction, and then send using the normal `eth_sendRawTransaction` using your `suaveClient` ```golang= mevShareAddrPtr, txHash, err := sendMevShareCreationTx(suaveClient, suaveSigner, privKey) if err != nil { panic(err.Error()) } waitForTransactionToBeConfirmed(suaveClient, txHash) mevShareAddr := *mevShareAddrPtr ``` Now we take a look under the hood of `sendMevShareCreationTx`. ```golang= func sendMevShareCreationTx(suaveClient *rpc.Client, suaveSigner types.Signer, privKey *ecdsa.PrivateKey) (*common.Address, *common.Hash, error) { var suaveAccNonceBytes hexutil.Uint64 err := suaveClient.Call( &suaveAccNonceBytes, "eth_getTransactionCount", crypto.PubkeyToAddress(privKey.PublicKey), "latest" ) suaveAccNonce := uint64(suaveAccNonceBytes) var suaveGp hexutil.Big err = suaveClient.Call(&suaveGp, "eth_gasPrice") calldata := hexutil.MustDecode(mevshareContractBytecode) mevshareContractBytecode) ccTxData := &types.LegacyTx{ Nonce: suaveAccNonce, To: nil, // contract creation Value: big.NewInt(0), Gas: 10000000, GasPrice: (*big.Int)(&suaveGp), Data: calldata, } tx, err := types.SignTx(types.NewTx(ccTxData), suaveSigner, privKey) from, _ := types.Sender(suaveSigner, tx) mevshareAddr := crypto.CreateAddress(from, tx.Nonce()) log.Info("contract address will be", "addr", mevshareAddr) txBytes, err := tx.MarshalBinary() var txHash common.Hash err = suaveClient.Call( &txHash, "eth_sendRawTransaction", hexutil.Encode(txBytes) ) return &mevshareAddr, &txHash, nil } ``` Later, we'll incorporate the `mevshareAddr` into our transaction's allowed contracts, granting access for the contract to compute over our confidential data. ### 2. Deploy Block Builder Contract 📜 Next we deploy a [simple block builder contract](https://github.com/flashbots/suave-geth/blob/4ad40edc58d8374c24a2e88c0b6fe2a7d8363ae3/suave/sol/standard_peekers/bids.sol#L140) which we will also store to later grant access to. The block builder takes in a `boostRelayUrl` which is where it will send blocks to when finished building. ```golang= blockSenderAddrPtr, txHash, err := sendBlockSenderCreationTx( suaveClient, suaveSigner, privKey, boostRelayUrl ) if err != nil { panic(err.Error()) } waitForTransactionToBeConfirmed(suaveClient, txHash) blockSenderAddr := *blockSenderAddrPtr ``` Similar as above, `sendBlockSenderCreationTx` operates like any other contract deployment on an EVM chain. ### 3. Send Mevshare Bundles 📨 Once our contracts have been succesfully deployed we will craft a goerli bundle and send it to our newly deployed mev-share contract. ```golang= mevShareTx, err := sendMevShareBidTx(suaveClient, goerliClient, suaveSigner, goerliSigner, 5, mevShareAddr, blockSenderAddr, executionNodeAddress, privKey) if err != nil { err = errors.Wrap(err, unwrapPeekerError(err).Error()) panic(err.Error()) } waitForTransactionToBeConfirmed(suaveClient, &mevShareTx.txHash) ``` Let's take a deeper look at `sendMevShareBidTx` which looks similar to a normal Ethereum transaction but has a couple key differences. We explore those below the following code snippet. ```golang= func sendMevShareBidTx( // function inputs removed for brevity ) (mevShareBidData, error) { var startingGoerliBlockNum uint64 err = goerliClient.Call( (*hexutil.Uint64)(&startingGoerliBlockNum), "eth_blockNumber" ) if err != nil { utils.Fatalf("could not get goerli block: %v", err) } _, ethBundleBytes, err := prepareEthBundle( goerliClient, goerliSigner, privKey ) // Prepare bundle bid var suaveAccNonce hexutil.Uint64 err = suaveClient.Call( &suaveAccNonce, "eth_getTransactionCount", crypto.PubkeyToAddress(privKey.PublicKey), "pending" ) confidentialDataBytes, err := mevShareABI.Methods["fetchBidConfidentialBundleData"].Outputs.Pack(ethBundleBytes) allowedPeekers := []common.Address{ newBlockBidAddress, extractHintAddress, buildEthBlockAddress, mevShareAddr, blockBuilderAddr } calldata, err := mevShareABI.Pack("newBid", blockNum, allowedPeekers) if err != nil { return mevShareBidData{}, err } wrappedTxData := &types.DynamicFeeTx{ Nonce: suaveAccNonce, To: &mevShareAddr, Value: nil, Gas: 10000000, GasTipCap: big.NewInt(10), GasFeeCap: big.NewInt(33000000000), Data: calldata, } mevShareTx, err := types.SignTx(types.NewTx(&types.OffchainTx{ ExecutionNode: executionNodeAddr, Wrapped: *types.NewTx(wrappedTxData), }), suaveSigner, privKey) if err != nil { return nil, nil, err } mevShareTxBytes, err := mevShareTx.MarshalBinary() if err != nil { return nil, nil, err } var offchainTxHash common.Hash err = suaveClient.Call( &offchainTxHash, "eth_sendRawTransaction", hexutil.Encode(mevShareTxBytes), hexutil.Encode(confidentialDataBytes) ) if err != nil { return mevShareBidData{}, err } mevShareTxHash= mevShareBidData{blockNumber: blockNum, txHash: offchainTxHash} return mevShareTxHash, nil } ``` A SUAVE transaction, referred to as a mevshare bid in the code, takes in two extra arguments: `allowedPeekers` and `executionNodeAddr`. These arguement are to utilize a new transaction primitive `types.OffchainTx`, which you can read more about [here](https://github.com/flashbots/suave-geth/tree/suave-poc/suave#off-chain-transactions). The role of `allowedPeekers` is to dictate which contracts can view the confidential data, in our scenario, the goerli bundle being submitted. Meanwhile, `executionNodeAddr` points to the intended execution node for the transaction. Lastly, Suave nodes have a modified `ethSendRawTransaction` to support this new transaction type. ### 4. Send Mevshare Matches 🎯 Now that a MEV-share bid has been sent in we can simulate sending in a match. Once live on a testnet, searchers can monitor the SUAVE chain looking for hints emitted as logs for protocols they specialize in. In our example you could monitor the `mevShareAddr` for emitted events. Using these hints they can get a `BidId` to reference in their match. Below we see the code. ```golang= bidIdBytes, err := extractBidId(suaveClient, mevShareTx.txHash) if err != nil { panic(err.Error()) } _, err = sendMevShareMatchTx( suaveClient, goerliClient, suaveSigner, goerliSigner, mevShareTx.blockNumber, mevShareAddr, blockSenderAddr, executionNodeAddress, bidIdBytes, privKey, ) if err != nil { err = errors.Wrap(err, unwrapPeekerError(err).Error()) panic(err.Error()) } ``` ### 5. Build Block and Relay 🧱 Now that our SUAVE node's bidpool has a mevshare bid and match, we can trigger block building to combine these transactions, simulate for validity, and insert the refund transaction. ```golang= _, err = sendBuildShareBlockTx(suaveClient, suaveSigner, privKey, executionNodeAddress, blockSenderAddr, payloadArgsTuple, uint64(goerliBlockNum)+1) if err != nil { err = errors.Wrap(err, unwrapPeekerError(err).Error()) if strings.Contains(err.Error(), "no bids") { log.Error("Failed to build a block, no bids") } log.Error("Failed to send BuildShareBlockTx", "err", err) } ``` Once the execution node has received this transaciton it will build your block and send it off to a relay. If you used the flashbots goerli relay you should be able to check it out using the [builder blocks received endpoint](https://boost-relay-goerli.flashbots.net/). ## Conclusion 🎓 This walkthrough has gone through how to deploy MEV-Share and block builder contracts, send in a basic MEV-Share bid and match, and trigger building a block and sending to a relay, all in golang! For further elucidation, the SUAVE repository has a wealth of information, and we will continue to be releasing new walkthroughs and tutorials.