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.
- Suave JSON-RPC has two modes of operation: regular and confidential. Which is activated depends on the truth value of
IsConfidential
in any request received.- Regular mode is equivalent to the usual EVM.
- Confidential mode accesses additional precompiles, both directly and through a convenient library.
- A new optional argument -
confidential_data
- is added toeth_sendRawTransaction
,eth_sendTransaction
andeth_call
methods. - All RPCs that return transaction or receipt objects will do so with type
SuaveTransaction
, a super set of regular Ethereum transactions.
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.
- Request faucet funds here.
- 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("0626a5c68af88e43613f7f7cf5879d60f50033b3")
)
The exact details for the Rigil Testnet are:
- Network ID:
16813125
- Chain ID:
16813125
- RPC Endpoint:
https://rpc.rigil.suave.flashbots.net
- Faucet: https://faucet.rigil.suave.flashbots.net
- Explorer: https://explorer.rigil.suave.flashbots.net