Skip to main content

Send Transactions on SUAVE

If you followed the previous guide, you will have SUAVE running locally, either in Docker or via the binaries themselves.

In either case, you can cause a series of transactions to occur by running:

go run suave/devenv/cmd/main.go

[🔗source]

This guide will show you how to craft such transactions yourself.

RPC and SDK

In order to keep some data in transactions confidential, SUAVE JSON-RPC extends the usual Ethereum JSOPN-RPC methods. Some methods in the eth_ namespace are overloaded to support confidential compute requests.

  1. eth_sendRawTransaction

Creates a new message call transaction or a contract creation for any signed ConfidentialComputeRequest.

  1. eth_call

Executes a new message call immediately without creating a transaction on the block chain. It follows the same format as the default eth_call with two extra parameters:

Parameters

  • IsConfidential: Set to true to execute as a confidential request and access the MEVM methods.
  • ExecutionAddress: address - (optional) The execution address that performs the execution.
  1. eth_executionAddress

Returns the list of available addresses in the Kettle to execute the confidential compute request.

The SUAVE SDK makes it easy to interact with the extended RPC and we will be using it in this guide.

1. Fund a local account

Create a new file in suave/devenv/cmd called transactions.go:

package main

import (
"context"
"crypto/ecdsa"
"fmt"
"math/big"
"os"

_ "embed"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/suave/sdk"
)

var (
// This is the address we used when starting the MEVM
exNodeEthAddr = common.HexToAddress("b5feafbdd752ad52afb7e1bd2e40432a485bbb7f")
exNodeNetAddr = "http://localhost:8545"
// This account is funded in both devenv networks
// address: 0xBE69d72ca5f88aCba033a063dF5DBe43a4148De0
fundedAccount = newPrivKeyFromHex(
"91ab9a7e53c220e6210460b65a7a3bb2ca181412a8a7b43ff336b3df1737ce12"
)
)

func main() {
rpcClient, _ := rpc.Dial(exNodeNetAddr)
// Use the SDK to create a new client by specifying the Eth Address of the MEVM
mevmClt := sdk.NewClient(rpcClient, fundedAccount.priv, exNodeEthAddr)

testAddr1 := generatePrivKey()

fundBalance := big.NewInt(100000000)
if err := fundAccount(mevmClt, testAddr1.Address(), fundBalance); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
return
}
fmt.Printf("Funded test account: %s (%s)\n", testAddr1.Address().Hex(), fundBalance.String())
}

func fundAccount(clt *sdk.Client, to common.Address, value *big.Int) error {
txn := &types.LegacyTx{
Value: value,
To: &to,
}
result, err := clt.SendTransaction(txn)
if err != nil {
return err
}
_, err = result.Wait()
if err != nil {
return err
}
// check balance
balance, err := clt.RPC().BalanceAt(context.Background(), to, nil)
if err != nil {
return err
}
if balance.Cmp(value) != 0 {
return fmt.Errorf("failed to fund account")
}
return nil
}

// General types and methods we need for the above to work as we want it to,
// nothing SUAVE specific
type privKey struct {
priv *ecdsa.PrivateKey
}

func (p *privKey) Address() common.Address {
return crypto.PubkeyToAddress(p.priv.PublicKey)
}

func newPrivKeyFromHex(hex string) *privKey {
key, err := crypto.HexToECDSA(hex)
if err != nil {
panic(fmt.Sprintf("failed to parse private key: %v", err))
}
return &privKey{priv: key}
}

func generatePrivKey() *privKey {
key, err := crypto.GenerateKey()
if err != nil {
panic(fmt.Sprintf("failed to generate private key: %v", err))
}
return &privKey{priv: key}
}

If you run the following from your terminal, you should now have one funded account:

go run suave/devenv/cmd/transactions.go

The important parts to note are the SendTransaction method, and the way the balance of a given account is fetched via the RPC() method, both available in the SDK. Using this pattern, you should be able to send most of the transactions you wish to, as well as fetch information about other accounts or transactions as is necessary.

2. Deploy a contract

We've written a number of example smart contracts to help get you started thinking about what's possible. For this guide, we'll stick to deploying one of these examples to keep things simple.

Create a new file called deploy.go:

package main

import (
"crypto/ecdsa"
"fmt"

_ "embed"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/suave/e2e"
"github.com/ethereum/go-ethereum/suave/sdk"
)

var (
// This is the address we used when starting the MEVM
exNodeEthAddr = common.HexToAddress("b5feafbdd752ad52afb7e1bd2e40432a485bbb7f")
exNodeNetAddr = "http://localhost:8545"
// This account is funded in both devenv networks
// address: 0xBE69d72ca5f88aCba033a063dF5DBe43a4148De0
fundedAccount = newPrivKeyFromHex(
"91ab9a7e53c220e6210460b65a7a3bb2ca181412a8a7b43ff336b3df1737ce12"
)
)

var (
mevShareArtifact = e2e.MevShareBidContract
)

func main() {
rpcClient, _ := rpc.Dial(exNodeNetAddr)
mevmClt := sdk.NewClient(rpcClient, fundedAccount.priv, exNodeEthAddr)

var mevShareContract *sdk.Contract
_ = mevShareContract

txnResult, err := sdk.DeployContract(mevShareArtifact.Code, mevmClt)
if err != nil {
fmt.Errorf("Failed to deploy contract: %v", err)
}
receipt, err := txnResult.Wait()
if err != nil {
fmt.Errorf("Failed to wait for transaction result: %v", err)
}
if receipt.Status == 0 {
fmt.Errorf("Failed to deploy contract: %v", err)
}

fmt.Printf("- Example contract deployed: %s\n", receipt.ContractAddress)
mevShareContract = sdk.GetContract(receipt.ContractAddress, mevShareArtifact.Abi, mevmClt)
}

// Helpers, not unique to SUAVE

type privKey struct {
priv *ecdsa.PrivateKey
}

func newPrivKeyFromHex(hex string) *privKey {
key, err := crypto.HexToECDSA(hex)
if err != nil {
panic(fmt.Sprintf("failed to parse private key: %v", err))
}
return &privKey{priv: key}
}

If you now run:

go run suave/devenv/cmd/deploy.go

You should see the address of your new example contract printed in the terminal.

The important parts to note when deploying contracts are the call to e2e, which helps generate ABIs and bytecode for contracts, and the sdk.DeplyContract and sdk.GetContract.

If you're able to generate the necessary ABIs and bytecode, you should be able to deploy any contract you like using the above pattern.

3. Use Rigil Testnet RPC

You can also adapt any of this code to use the Rigil Testnet RPC, rather than your local development environment.

  1. Request faucet funds here.
  2. Change this code:
var (
// Target the Rigil RPC
exNodeNetAddr = "https://rpc.rigil.suave.flashbots.net"
// Insert a private key you own with some SUAVE ETH
fundedAccount = newPrivKeyFromHex("<your_priv_key>")
// The public address of a Kettle on Rigil
exNodeEthAddr = common.HexToAddress("03493869959c866713c33669ca118e774a30a0e5")
)

The exact details for the Rigil Testnet are: